/*!
 * 通用工具JS
 * 赵卉华 / zhaohuihua@126.com
 */
+function() {
	if (!String.prototype.startsWith) {
		String.prototype.startsWith = function(s) {
			return s && s.length < this.length && s == this.substring(0, s.length);
		};
	}
	if (!String.prototype.endsWith) {
		String.prototype.endsWith = function(s) {
			return s && s.length < this.length && s == this.substring(this.length - s.length);
		};
	}

	// 修复toFixed问题:  (1.335).toFixed(2) = 1.33, 测试版本chrome.70.0
	var toFixed = Number.prototype.toFixed;
	Number.prototype.toFixed = function(decimal) {
		var times = Math.pow(10, decimal)
		var number = parseInt(this * times + 0.5, 10) / times;
		return toFixed.call(number, decimal);
	};

	// KB/MB/GB/TB/PB/EB/ZB/YB
	var kibibyte = 1024;
	var mebibyte = kibibyte * kibibyte;
	var gibibyte = mebibyte * kibibyte;
	var tebibyte = gibibyte * kibibyte;
	var pebibyte = tebibyte * kibibyte;
	var exbibyte = pebibyte * kibibyte;
	var zebibyte = exbibyte * kibibyte;
	var yobibyte = zebibyte * kibibyte;
	/**
	 * 转换为Byte描述字符串
	 *
	 * @return B/KB/MB/GB/TB/PB/EB/ZB/YB
	 */
	if (!Number.prototype.toByteString) {
		var format = function(size) {
			return size.toFixed(2).replace(/0+$/, "").replace(/\.$/, "");
		};
		Number.prototype.toByteString = function() {
			if (this < kibibyte) {
				return this + "B";
			} else if (this < mebibyte) {
				return format(1.0 * this / kibibyte) + "KB";
			} else if (this < gibibyte) {
				return format(1.0 * this / mebibyte) + "MB";
			} else if (this < tebibyte) {
				return format(1.0 * this / gibibyte) + "GB";
			} else if (this < pebibyte) {
				return format(1.0 * this / tebibyte) + "TB";
			} else if (this < exbibyte) {
				return format(1.0 * this / pebibyte) + "PB";
			} else if (this < zebibyte) {
				return format(1.0 * this / exbibyte) + "EB";
			} else if (this < yobibyte) {
				return format(1.0 * this / zebibyte) + "ZB";
			} else {
				return format(1.0 * this / yobibyte) + "YB";
			}
		};
	}
}();

+function($) {
	if (!$.log) { $.log = {}; }
	$.log.debug = function(msg) {
		window.console && window.console.debug && window.console.debug(msg);
	};
	$.log.info = function(msg) {
		window.console && window.console.info && window.console.info(msg);
	};
	$.log.warn = function(msg) {
		window.console && window.console.warn && window.console.warn(msg);
	};
	$.log.error = function(msg) {
		window.console && window.console.error && window.console.error(msg);
	};
}(jQuery);


/**
 * -------------------------------------------------------------
 * 转换为JSON字符串, 会清除key的引号, 会清除value=null的字段, 双引号会替换为单引号
 * -------------------------------------------------------------
 * @author 赵卉华
 * date: 2019-04-30
 * -------------------------------------------------------------
 */
+function($) {
	if (!$.zhh) { $.zhh = {}; }
	$.zhh.toJsonString = function(object) {
		if (object == null) { return null; }
		return JSON.stringify(object).replace(/'/g,"\\'").replace(/"/g,"'")
			.replace(/'(\w+)':\s*null,/g, '').replace(/,'(\w+)':\s*null/g, '')
			.replace(/'(\w+)':/g, '$1:');
	};
}(jQuery);


/**
 * -------------------------------------------------------------
 * 根据指定的字段值获取JSON字段值
 * @param json JSON对象, 如:
 *    var json = { domain:{ text:"baidu", url:"http://baidu.com" } }
 * @param fields 字段名, 支持多级, 支持数组, 支持或运算
 *    如: "domain.text", "address[0]", "body.list || body"
 * @return JSON对象的字段值
 *    如: $.zhh.field(json, "domain.text"); 返回 "baidu"
 * -------------------------------------------------------------
 * 根据指定的字段值获取JSON数组的字段值
 * @param array JSON数组对象, 如:
 * var list =
 * [{ domain:{ text:"baidu", url:"http://baidu.com" } },
 *  { domain:{ text:"bing", url:"http://cn.bing.com" } }]
 * @param fields 字段名, 支持多级, 支持数组, 支持或运算
 *    如: "domain.text", "address[0]", "body.list || body"
 * @return JSON对象字段值的数组
 *    如: $.zhh.field(list, "domain.text"); 返回 ["baidu","bing"]
 * -------------------------------------------------------------
 * @author 赵卉华
 * date: 2014-03-12
 * -------------------------------------------------------------
 */
+function($) {
	// 或运算符
	var OR = /\s*\|\|\s*/;
	// 数组或点正则表达式
	var SPLITOR = /[\[\]\.]+/;

	var handle = function(json, fields) {
		var value = undefined;
		for (var i = 0; i < fields.length; i++) {
			// 根据字符串当中的表达式从json对象上取值
			value = json;
			// 把表达式以.或[]拆分为数组
			var list = fields[i].split(SPLITOR);
			// 逐层取值
			for (var j = 0; value != undefined && j < list.length; j++) {
				if (list[j] && list[j] != 'this') { value = value[list[j]]; }
			}
			if (value) { break; }
		}
		return value;
	};
	if (!$.zhh) { $.zhh = {}; }
	$.zhh.field = function(json, field) {
		var fields = field.split(OR);
		if ($.isArray(json)) {
			var list = [];
			for (var i = 0; i < json.length; i++) {
				list.push(handle(json[i], fields));
			}
			return list;
		} else {
			return json == null ? null : handle(json, fields);
		}
	};
}(jQuery);


/**
 * -------------------------------------------------------------
 * 复制数组或JSON对象, 第一个参数是数组就是复制数组, 第一个参数是JSON对象就是复制JSON对象
 * $.zhh.copy([{a:1},{b:2}], {c:3}, undefined, {d:4}, [{e:5}], 6, [7,8]);
 *   --> [ {a:1}, {b:2}, {c:3}, {d:4}, {e:5}, 6, 7, 8 ]
 * -------------------------------------------------------------
 * @author 赵卉华
 * date: 2014-03-12
 * -------------------------------------------------------------
 */
+function($) {
	var copy = function(object) {
		if ($.isArray(object)) {
			var array = object;
			for (var i = 1; i < arguments.length; i++) {
				var item = arguments[i];
				if (item == null) { continue; }
				if ($.isArray(item)) {
					$.each(item, function(i, v) {
						if ($.isArray(v) || $.isPlainObject(v)) {
							array.push(copy(v));
						} else {
							array.push(v);
						}
					});
				} else if ($.isPlainObject(item)) {
					array.push($.extend(true, {}, item));
				} else {
					array.push(item);
				}
			}
			return array;
		} else if ($.isPlainObject(object)) {
			var json = object;
			for (var i = 1; i < arguments.length; i++) {
				if (arguments[i] != null) { $.extend(true, json, arguments[i]); }
			}
			return json;
		} else {
			return object;
		}
	};
	if (!$.zhh) { $.zhh = {}; }
	$.zhh.copy = copy;
}(jQuery);



/**
 * -----------------------------------------------------------
 * 连接URL和参数
 * $.zhh.concatUrlParams(url, params);
 * -----------------------------------------------------------
 * JSON数据和参数相互转换, 支持复杂对象的提交
 * -----------------------------
 * $.zhh.jsonToParams(data, keepArray); // 转换为提交参数
 * keepArray, optional, 如果数组中全是简单对象, 是否保留数组形式
 * var params = $.zhh.jsonToParams(data);
 * $.post(url, params, ...);
 * -----------------------------
 * $.zhh.toDeepJson(data); // 还原为JSON数据
 * -----------------------------
    var data = {
        "name":"某公司",
        "address":{"city":"南京"},
        "users":[
            {"name":"张三","addresses":[{"city":"南京"}]},
            {"name":"李四","addresses":[{"city":"合肥"},{"city":"南京"}]}
        ]
    });
    
    var params = {
        "name":"某公司",
        "address.city":"南京",
        "users[0].name":"张三",
        "users[0].addresses[0].city":"南京",
        "users[1].name":"李四",
        "users[1].addresses[0].city":"合肥",
        "users[1].addresses[1].city":"南京"
    };
 * -----------------------------------------------------------
 * author: 赵卉华 / 2015-10-10
 * -----------------------------------------------------------
 */
+function($) {
	if (!$.zhh) { $.zhh = {}; }
	var isSimpleArray = function(array) {
		for (var i = 0; i < array.length; i++) {
			var value = array[i];
			if ($.isPlainObject(value) || $.isArray(value)) {
				return false;
			}
		}
		return true;
	};
	var jsonToParams = function(data, buffer, preffix, keepArray) {
		if ($.isPlainObject(data)) { // Object
			for (var key in data) {
				var newPreffix = preffix ? preffix + "." + key : key;
				jsonToParams(data[key], buffer, newPreffix, keepArray);
			}
		} else if ($.isArray(data)) { // Array
			if (keepArray && isSimpleArray(data)) {
				buffer[preffix] = data;
			} else {
				for (var i = 0; i < data.length; i++) {
					jsonToParams(data[i], buffer, preffix + "[" + i + "]", keepArray);
				}
			}
		} else if ($.dt.isDate(data)) { // Date
			buffer[preffix] = Dates.format(data, 'yyyy-MM-dd HH:mm:ss.SSS');
		} else { // 基本数据类型
			buffer[preffix] = data;
		}
	};
	$.zhh.jsonToParams = function(data, keepArray) {
		if (!$.isPlainObject(data) && !$.isArray(data)) { return data; }
		var buffer = {};
		jsonToParams(data, buffer, "", keepArray);
		return buffer;
	};

	$.zhh.toDeepJson = function(data) {
		var map = { };
		for (var field in data) {
			var value = data[field];
			// 把字段以.或[]拆分为数组
			// "users[1].address[0]" --> ["users", "1", "address", "0"]
			// 为防止出现第1段就是数组的情况, 在最前面加上固定的前缀$$$
			var list = ("$$$." + field).replace(/^\[|\]$/g, "").split(/[\[\]\.]+/);
			var temp = map;
			// 逐层设值
			for (var i = 0; i < list.length; i++) {
				var key = list[i];
				if (i == list.length - 1) { // 最后一层
					temp[key] = value;
				} else if (temp[key] == undefined) {
					if (/^[0-9]+$/.test(list[i + 1])) {
						temp[key] = [];
					} else {
						temp[key] = {};
					}
				}
				temp = temp[key];
			}
		}
		return map.$$$;
	};
	$.zhh.concatUrlParams = function(url, json) {
		if (!json || $.isEmptyObject(json)) {
			return url;
		}
		var params = $.zhh.jsonToParams(json);
		var keys = [];
		for (var key in params) {
			keys.push(key);
		}
		keys.sort();
		var map = {};
		$.each(keys, function(i, key) {
			map[key] = params[key];
		});
		return url + "?" + $.param(map, true);
	};
}(jQuery);


/*!
 * $(form).serializeJson();
 * 将表单转化为JSON格式, 赵卉华, 2015-02-14
 */
+function($) {
	$.fn.serializeJson = function(deep) {
		var json = {};
		var array = this.serializeArray();
		$.each(array, function() {
			var name = this.name;
			var value = this.value;
			if (value == "") { return true; }
			var old = json[name];
			if (old) {
				if ($.isArray(old)) {
					old.push(value);
				} else {
					json[name] = [old, value];
				}
			} else {
				json[name] = value;
			}
		});
		// 支持<input type="checkbox" value="true"/>作为boolean值
		this.find("input:enabled:checkbox[value=true]").each(function() {
			if (this.name) {
				json[this.name] = this.checked;
			}
		});
		return deep ? $.zhh.toDeepJson(json) : json;
	};
	$.fn.formParam = function(){
		var json = this.serializeJson();
		var string = [];
		for (var key in json) {
			var value = json[key];
			if (value == null || value == "") { continue; }
			if ($.isArray(value)) {
				for (var i = 0; i < value.length; i++) {
					string.push(key+"="+value[i]);
				}
			} else {
				string.push(key+"="+value);
			}
		}
		return string.join("&");
	};
}(jQuery);


+function($) {
	/**
	 * -----------------------------------------------------------
	 * 支持复杂对象的赋值
	 * -----------------------------------------------------------
	 * $.fn.fillForm(data)
	 * $.fn.fillForm(createElemIfNotExist, data)
	 * createElemIfNotExist:true|false 如果DomElement不存在, 是否需要创建
	 * -----------------------------------------------------------
	 * $.fn.fillForm(name, array) 数组的赋值
	 * -----------------------------------------------------------
		$(form).fillForm({
			"name":"某公司","address":{"city":"南京"},
			"users":[
				{"name":"张三","addresses":[{"city":"南京"}]},
				{"name":"李四","addresses":[{"city":"合肥"},{"city":"南京"}]}
			]
		});
		<form>
			<input name="name" />
			<input name="address.city" />
			<input name="users[0].name" />
			<input name="users[0].addresses[0].city" />
			<input name="users[1].name" />
			<input name="users[1].addresses[0].city" />
			<input name="users[1].addresses[1].city" />
		</form>
	 * -----------------------------------------------------------
		$(form).fillForm("ids", [1001, 1002, 1005, 1008]);
		<form>
			<input type="hidden" name="ids" value="1001" />
			<input type="hidden" name="ids" value="1002" />
			<input type="hidden" name="ids" value="1005" />
			<input type="hidden" name="ids" value="1008" />
		</form>
	 * -----------------------------------------------------------
	 * author: 赵卉华 / 2015-10-10 / 2016-10-15 / 2018-07-27
	 * -----------------------------------------------------------
	 */
	var hidden = '<input type="hidden" name="{0}" value="{1}" />';
	$.fn.fillForm = function(one, two) {
		if ($.dt.isString(one)) { // 数组赋值
			var name = one;
			var array = two;
			this.find("input[type=hidden][name='" + name + "']").remove();
			for (var i = 0, len = array ? array.length : 0; i < len; i++ ) {
				// if (array[i] == undefined) { continue; } // 空的也需要占位
				this.append($.zhh.format(hidden, name, array[i]));
			}
		} else {
			var createElemIfNotExist = undefined;
			var data = undefined;
			if ($.isPlainObject(one)) { createElemIfNotExist = false; data = one;  } 
			else if ($.dt.isBoolean(one)) { createElemIfNotExist = one; data = two; }
			else if (one != null) { $.log.debug("Can't fill form, argument type error."); }
			if (data == null) { return; }
			var plainParams = $.zhh.jsonToParams(data);
			var elemMap = {};
			this.find('input,select,textarea').each(function() {
				var key = this.name;
				if (!key) { return true; }
				if (elemMap[key]) { elemMap[key].push(this); }
				else { elemMap[key] = $(this); }
			});
			for (key in elemMap) {
				var elem = elemMap[key];
				var value = plainParams[key];
				if (elem == undefined || value == undefined) { continue; }
				delete plainParams[key];
				elem.each(function() {
					if (this.type == 'checkbox' || this.type == 'radio') {
						this.checked = typeof(value) == 'boolean' ? value : this.value == String(value);
					} else if (this.tagName == 'SELECT') {
						$(this).val(value);
					} else {
						this.value = String(value);
					}
				});
			}
			if (createElemIfNotExist) {
				var buffer = [];
				for (key in plainParams) {
					var value = plainParams[key];
					// if (value == undefined) { continue; } // 空的也需要占位
					buffer.push($.zhh.format(hidden, key, value));
				}
				buffer.length && this.append(buffer.join("\n"));
			}
		}
		return this;
	};

	// copy form http://malsup.github.io/jquery.form.js v3.51.0
	/**
	 * Clears the form data.  Takes the following actions on the form's input fields:
	 *  - input text fields will have their 'value' property set to the empty string
	 *  - select elements will have their 'selectedIndex' property set to -1
	 *  - checkbox and radio inputs will have their 'checked' property set to false
	 *  - inputs of type submit, button, reset, and hidden will *not* be effected
	 *  - button elements will *not* be effected
	 *  支持通过data-clear="false"设置字段不允许清除
	 */
	var re = /^(?:color|date|datetime|email|month|number|password|range|search|tel|text|time|url|week)$/i; // 'hidden' is not in this list
	$.fn.clearForm = function(includeHidden) {
		return this.each(function() {
			$('input,select,textarea', this).each(function() {
				if ($(this).attr("data-clear") == "false") {
					return true;
				}
				var t = this.type, tag = this.tagName.toLowerCase();
				if (re.test(t) || tag == 'textarea' || (t == 'hidden' && includeHidden == undefined)) {
					this.value = '';
				}
				else if (t == 'checkbox' || t == 'radio') {
					this.checked = false;
				}
				else if (tag == 'select') {
					this.value = '';
				}
				else if (t == "file") {
					if (/MSIE/.test(navigator.userAgent)) {
						$(this).replaceWith($(this).clone(true));
					} else {
						$(this).val('');
					}
				}
				else if (includeHidden) {
					// includeHidden can be the value true, or it can be a selector string
					// indicating a special test; for example:
					//  $('#myForm').clearForm('.special:hidden')
					// the above would clean hidden inputs that have the class of 'special'
					if ( (includeHidden === true && /hidden/.test(t)) ||
						 (typeof includeHidden == 'string' && $(this).is(includeHidden)) ) {
						this.value = '';
					}
				}
				$(this).trigger("clear");
			});
		});
	};
}(jQuery);



/**
 * -------------------------------------------------------------
 * 字符串格式化
 * 示例: $.zhh.format("Name:{}, Email:{}", "zhaohuihua", "zhaohuihua@126.com")
 * 示例: $.zhh.format("Name:{1}, Email:{0}", "zhaohuihua@126.com", "zhaohuihua")
 * --> Name:zhaohuihua, Email:zhaohuihua@126.com
 * 示例: $.zhh.format("Options format error, {}, element is {}", "type is required", element);
 * --> Options format error, type is required, element is <input name="xx" data-options="{value:0}">
 * -------------------------------------------------------------
 * 字符串加载JSON数据, 是字符串格式化的加强版
示例:
var json = {
	id:1001, name:"Avril Lavigne", extra:{download:1888, click:1999},
	image:[{type:"pvw",path:"1001.1.jpg"},{type:"main",path:"1001.2.jpg"}],
	music:["Runaway", "Innocence", "Contagious"]
};
$.zhh.format("<li>{id}</li><li>{name}</li>", json)
--> <li>1001</li><li>Avril Lavigne</li>
$.zhh.format("<li>{extra.click}</li><li>{image[1].path}</li>", json)
--> <li>1999</li><li>1001.2.jpg</li>
$.zhh.format("<li>{music[0]}</li><li>{music[1]}</li><li>{music[2]}</li>", json)
--> <li>Runaway</li><li>Innocence</li><li>Contagious</li>
 * -------------------------------------------------------------
 * $.zhh.format('{{}}', "<li>{{id}}</li><li>{{name}}</li>", json)
 * $.zhh.format('<##>', "<li><#id#></li><li><#name#></li>", json)
 * -------------------------------------------------------------
 * author: 赵卉华
 * date: 2011-01-05
 * -------------------------------------------------------------
 */
+function($) {
	// 加载JSON数据的正则表达式, 首字母不能为数字, 以免与格式化冲突
	if (!$.zhh) { $.zhh = {}; }
	// type: 双分隔符{{}}, 单分隔符{}
	$.zhh.format = function(type, string, json) {
		// var args = Array.prototype.splice.call(arguments, 1); // IE8返回空数组
		var args = [];
		if ($.zhh.format.regexp[type]) {
			for (var i = 2; i < arguments.length; i++) { args.push(arguments[i]); }
		} else {
			for (var i = 1; i < arguments.length; i++) { args.push(arguments[i]); }
			json = string; string = type; type = '{}';
		}
		var regexp = $.zhh.format.regexp[type];
		if (string == null) { return string; }
		if (regexp.json.exec(string)) {
			// 加载JSON数据, 也就是符合格式的表达式替换为JSON的属性值
			if ($.isPlainObject(json) || $.isArray(json)) { // 参数是JSON/Array才能执行
				return string.replace(regexp.json, function(old, exp) {
					var value = $.zhh.field(json, exp);
					return $.zhh.format.valueToString(value);
				});
			}
		} else if (regexp.number.exec(string)) {
			// 格式化, 也就是将符合格式的表达式替换为输入参数
			var i = -1;
			return string.replace(regexp.number, function(old, index) {
				i ++;
				var realIndex = index == null || index == "" ? i : parseInt(index);
				var value = args[realIndex];
				return $.zhh.format.valueToString(value);
			});
		}
		return string;
	};
	$.zhh.format.regexp = {
		'{}':   { number:  /\{(\d{0,2})\}/g,   json:  /\{([^0-9][^{}]*)\}/g   },
		'{{}}': { number:/\{\{(\d{0,2})\}\}/g, json:/\{\{([^0-9][^{}]*)\}\}/g },
		'<##>': { number:  /<#(\d{1,2})#>/g,   json:  /<#([^0-9][^<#>]*)#>/g  }
	};
	$.zhh.format.valueToString = function(value) {
		if (value == null) { return ""; }
		if ($.dt.isString(value)) { return value; }
		if ($.dt.isDate(value)) { return Dates.format(value).replace(/\s00:00:00$/, ""); }
		if ($.isPlainObject(value) || $.isArray(value)) {
			try { return $.zhh.toJsonString(value); } catch (e) { return value.toString(); }
		}
		if (value instanceof $ && value.length == 0) { // 空的jQuery对象转换为字符串
			return 'jQuery(length=0, selector="' + value.selector + '")';
		}
		if ($.dt.isElement(value)) { // DOM节点转换为字符串
			var $element = $(value);
			// 如果没有子节点, 直接返回outerHTML
			if ($element.children().length == 0) {
				return $element.prop("outerHTML").replace(/[\r\n]/g, '').replace(/\s+/g, ' ');
			}
			// 如果有子节点, 则取当前节点的tagName和attributes拼成字符串
			var tagName = $element.prop("tagName").toLowerCase();
			var buffer = [];
			buffer.push('<' + tagName);
			var attrs = $element.prop("attributes");
			for (var i = 0; i < attrs.length; i++) {
				var name = attrs[i].name;
				var value = attrs[i].value;
				if (value == null || value == "" || value == "null") { // IE7很多属性="null"
					if ($.inArray(name, ["disabled", "readonly", "checked", "selected"]) < 0) { continue; }
					buffer.push(' ' + name);
				} else {
					buffer.push(' ' + name + '="' + value + '"');
				}
			}
			buffer.push('></' + tagName + '>');
			return buffer.join('');
		}
		return value.toString();
	};
}(jQuery);


/**
 * -------------------------------------------------------------
 * 追加字符
 * -------------------------------------------------------------
	// 没有指定追加什么字符, 字符串默认追加空格, 数字默认追加0
	$.zhh.pad(12345, 10);  // 返回 "0000012345"
	$.zhh.pad("12345", 10);  // 返回 "     12345"
	$.zhh.pad("12345", '_', 10);  // 返回 "_____12345"
	$.zhh.pad("12345", '_', false, 10);  // 返回 "12345_____"
 * -------------------------------------------------------------
 * author: 赵卉华
 * date: 2018-12-05
 * -------------------------------------------------------------
 */
+function($) {
	if (!$.zhh) { $.zhh = {}; }
	$.zhh.pad = function(string, c, left, length) {
		if ($.dt.isNumber(c)) {
			length = c;
			c = $.dt.isNumber(string) ? '0' : ' ';
			left = true;
		} else if ($.dt.isBoolean(c)) {
			length = left;
			left = c;
			c = $.dt.isNumber(string) ? '0' : ' ';
		} else if ($.dt.isNumber(left)) {
			length = left;
			left = true;
		}
		string = String(string);
		var temp = [];
		for (var i = string.length; i < length; i++) {
			temp.push(c);
		}
		return left ? temp.join("") + string : string + temp.join("");
	};
}(jQuery);


+function($) {
	/**
	 * -----------------------------------------------------------
	 * 填充数据
	 * $.fn.fillData(data); // 填充数据
	 * $.fn.fillData("clear"); // 清空数据
	 * $.fn.fillData(string); // 全部填充为指定字符串, fillData("..."):全部填充为...
	 * numeral需要用到assets/libs/numeral/min/numeral.min.js
	 * -----------------------------------------------------------
	 	金额:<span data-fill="amount"></span>元
	 	<span data-fill="field:'amount',prefix:'金额:',suffix:'元',defaultValue:'0.00',format:{numeral:'0,0.00'}"></span>
	 	生日:<span data-fill="field:'birthday',defaultValue:'{date}',format:{date:'yyyyMMdd'}"></span>
	 	性别:<span data-fill="field:'gender',defaultValue:'UNKNOWN',format:{map:{MALE:'男',FEMALE:'女',UNKNOWN:'未知'}}"></span>
	 	手机:<span data-fill="field:'phone',defaultValue:'UNKNOWN',defaultClass:'color-weak'"></span>
		$(document.body).fillData({ gender:'MALE', birthday:'2016-08-08', amount:888 });
	 * -----------------------------------------------------------
	    disableWith or enableWith or showWith or hideWith: {state:'NORMAL',gender:['MALE','FEMALE']}
	    activeWith: {state:'NORMAL',gender:['MALE','FEMALE']} // 满足条件时加上active样式, 否则删除active样式
	    classOnExist: { field:'phone', class:'active' } // 存在该字段时加上指定样式, 否则删除样式
	    classOnNotExist: { field:'phone', class:'color-weak' } // 不存在该字段时加上指定样式, 否则删除样式
	    <div data-fill='showWith:{state:'NORMAL'}'> ... </div>
	    <button data-fill='enableWith:{state:'NORMAL'}'> ... </button>
	 * -----------------------------------------------------------
	 * author: 赵卉华 / 2016-12-09
	 * -----------------------------------------------------------
	 */
	$.fn.fillData = function(data, options) {
    	var o = $.extend(true, {}, $.fn.fillData.defaults, options);
		var zokey = o.attr;
		var selector = "[data-" + zokey + "]";
	    return this.find(selector).each(function() {
	    	var me = $(this);
	    	if (data == "clear") { me.text(""); return true; }
	    	else if (!$.isPlainObject(data) && !$.isArray(data)) { me.text(data); return true; }

	    	var zo = me.zoptions(zokey);
	    	if (!$.isPlainObject(zo)) { zo = { field:zo }; }
	    	if (zo.field) {
		    	var value = o.getDataValue(data, zo.field);
		    	var isNull = o.isNullValue(value);
				if (zo.defaultClass) {
					me[isNull ? "addClass" : "removeClass"](zo.defaultClass);
				}
		    	if (isNull && zo.defaultValue == undefined) {
			    	if (this.tagName == 'INPUT' && (this.type == 'checkbox' || this.type == 'radio')) {
			            this.checked = false;
			        } else if (this.tagName == 'SELECT' || this.tagName == 'INPUT') {
			        	me.val("");
			    	} else {
			    		me.text("");
			    	}
		    	} else {
		    		if (isNull) {
		    			value = o.getDefaultValue(zo.defaultValue);
		    		}
		    		if (zo.format) {
		    			value = formatValue(value, zo.format);
		    		}
		    		value = (zo.prefix || "") + value + (zo.suffix || "");
			    	if (this.tagName == 'INPUT' && (this.type == 'checkbox' || this.type == 'radio')) {
			            this.checked = typeof(value) == 'boolean' ? value : this.value == String(value);
			    	} else if (this.tagName == 'SELECT' || this.tagName == 'INPUT' || this.tagName == 'TEXTAREA') {
			        	me.val(value);
			    	} else {
			    		me.html(value);
			    	}
		    	}
	    	}
	    	if (zo.disableWith || zo.enableWith || zo.showWith || zo.hideWith) {
	    		if (zo.disableWith) {
	    			var result = doDataStateCheck.call(this, data, zo.disableWith);
	    			result ? o.disableElement(me) : o.enableElement(me);
	    		} else if (zo.enableWith) {
	    			var result = doDataStateCheck.call(this, data, zo.enableWith);
	    			result ? o.enableElement(me) : o.disableElement(me);
	    		} else {
	    			o.enableElement(me);
	    		}
	    		if (zo.hideWith) {
	    			var result = doDataStateCheck.call(this, data, zo.hideWith);
	    			result ? o.hideElement(me) : o.showElement(me);
	    		} else if (zo.showWith) {
	    			var result = doDataStateCheck.call(this, data, zo.showWith);
	    			result ? o.showElement(me) : o.hideElement(me);
	    		} else {
	    			o.showElement(me);
	    		}
	    	}
	    	if (zo.activeWith) {
    			var result = doDataStateCheck.call(this, data, zo.activeWith);
    			result ? o.activeElement(me) : o.inactiveElement(me);
	    	}
	    	if (zo.classOnExist) {
	    		var exist = $.zhh.field(data, zo.classOnExist.field || zo.field) != null;
	    		var clazz = zo.classOnExist["class"] || ("exist-" + zo.classOnExist.field);
	    		exist ? me.addClass(clazz) : me.removeClass(clazz);
	    	}
	    	if (zo.classOnNotExist) {
	    		var exist = $.zhh.field(data, zo.classOnNotExist.field || zo.field) != null;
	    		var clazz = zo.classOnNotExist["class"] || ("exist-" + zo.classOnNotExist.field);
	    		exist ? me.removeClass(clazz) : me.addClass(clazz);
	    	}
	    });
	};
	$.fn.fillData.defaults = {
		attr:"fill",
		getDataValue: function(data, field) {
			return $.zhh.field(data, field);
		},
		isNullValue: function(value) {
			return value == undefined || value == "";
		},
		getDefaultValue: function(defaultValue) {
			return defaultValue == "{date}" ? new Date() : defaultValue;
		},
		enableElement: function(element) {
			element.removeClass("disabled").prop("disabled", false);
		},
		disableElement: function(element) {
			element.addClass("disabled").prop("disabled", true);
		},
		showElement: function(element) {
			element.removeClass("hide").show();
		},
		hideElement: function(element) {
			element.addClass("hide").hide();
		},
		activeElement: function(element) {
			element.addClass("active");
		},
		inactiveElement: function(element) {
			element.removeClass("active");
		},
		format: {
			date:function(value, option) {
				return o.isNullValue(value) ? value : Dates.format(value, option);
			},
			numeral:function(value, option) {
				return o.isNullValue(value) ? value : numeral(value).format(option);
			},
			map:function(value, map) {
				return o.isNullValue(value) ? value : map[value];
			}
		}
	};
	var formatValue = function(value, option) {
		for (var type in option) {
			var fn = $.fn.fillData.defaults.format[type];
			if (!fn) { $.log.error("Fill data format handle function not found: " + type); continue; }
			value = fn(value, option[type] || undefined);
		}
		return value;
	};
	// 根据数据状态决定节点的禁用/启用/隐藏/显示状态
	// data=当前数据, options=节点配置(如:{state:"NORMAL",gender:["MALE","FEMALE"]})
	var doDataStateCheck = function(data, options) {
		var result;
		if ($.isFunction(options)) {
			// disableWith or enableWith or showWith or hideWith: function(data) { return data.state == "NORMAL"; }
			result = options.call(this, data);
		} else {
			// disableWith or enableWith or showWith or hideWith: {state:"NORMAL",gender:["MALE","FEMALE"]}
			result = true;
			var list = $.isArray(data) ? data : [data];
			$.each(list, function(i, v) {
				for (var key in options) {
					var targetValues = $.isArray(options[key]) ? options[key] : [ options[key] ];
					var dataValue = $.zhh.field(v, key);
					if ($.inArray(dataValue, targetValues) < 0) {
						result = false; break;
					}
				}
				if (!result) { return false; }
			});
		}
		return result;
	};
}(jQuery);


+function($) {
	/**
	 * -----------------------------------------------------------
	 * tree数据解析器, 支持将扁平数组结构转换为tree层级结构
	 * -----------------------------------------------------------
	 * copy: { // 复制属性
	 *     text:"text || title || name",
	 *     icon:function(data) {  }, 
	 *     icon:{by:"level",map:{1:"home"}}
	 * }
	 * -----------------------------------------------------------
	 * $.zhh.treeparse(array, options)
	 * -----------------------------------------------------------
	 * author: 赵卉华 / 2014-03-26
	 * -----------------------------------------------------------
	 */

	// tree默认配置参数
	var OPTIONS = {
		copy: {}, // 需要复制的属性
		level: false, // 是否需要计算级别, String=LevelFieldName, 顶级节点=1
		upgrade: false, // 未找到父节点的子节点是否升级为根节点
		children: "children", // 用于记录子元素的KEY
		codeField: "id",
		parentField: "parent || parentId", // 用于关联的ID和父ID
		textField: "text || title || name",
		isRoot: function(parentCode, parentNode) {
			return parentCode == null || parentCode == "" || parentCode == "0" || parentCode == 0;
		}
	};
	// tree数据解析器
	if (!$.zhh) { $.zhh = {}; }
	$.zhh.treeparse = function(list, options) {
		if (list.length == 0) { return []; }
		var o = $.extend({}, OPTIONS, options);
		var data = [], buffer = {}, children = {};
		$.each(list, function() {
			var code = $.zhh.field(this, o.codeField); // 取当前ID
			var parent = $.zhh.field(this, o.parentField); // 取父节点ID
			if (code == parent) { parent = undefined; }
			if (o.isRoot(parent, this)) { // 顶级节点
				data.push(this);
			} else { // 不是顶级节点
				if (!children[parent]) {
					children[parent] = [this];
				} else if ($.inArray(this, children[parent]) < 0) {
					children[parent].push(this);
				}
			}
			buffer[code] = this; // 缓存当前节点, 有可能成为后面循环的父节点
		});
		// 为父节点设置子节点
		var ignores = [];
		for (var code in children) {
			if (buffer[code]) {
				buffer[code][o.children] = children[code];
			} else if (o.upgrade) { // 升级为根节点
				$.each(children[code], function() {
					data.push(this);
				});
			} else {
				ignores.push(code);
			}
		}
		if (ignores.length) {
			$.log.debug("ignore nodes: " + ignores.length + ", " + o.parentField + " = " + ignores.join(", "));
		}
		if (o.level) { // 计算级别
			var levelName = typeof (o.level) == "string" ? o.level : "level";
			var fillLevel = function(node) {
				$.each(node[o.children] || [], function() {
					this[levelName] = node[levelName] + 1;
					fillLevel(this);
				});
			};
			$.each(data, function() {
				this[levelName] = 1;
				fillLevel(this);
			});
		}
		if (o.copy && !$.isEmptyObject(o.copy)) {
			// 复制属性
			$.each(list, function() {
				for (var key in o.copy) {
					var value = o.copy[key];
					if ($.isFunction(value)) {
						var result = value(this, key, o);
						if (result !== undefined) {
							this[key] = result;
						}
					} else if ($.isPlainObject(value) && value.map) {
						if (value.by) {
							var targetValue = this[value.by];
							if (targetValue === undefined) { return true; }
							if (value.map[targetValue] !== undefined) {
								this[key] = value.map[targetValue];
							} else if (value.map['*'] !== undefined) {
								this[key] = value.map['*'];
							}
						}
					} else {
						this[key] = $.zhh.field(this, value);
					}
				}
			});
		}
		return data;
	};
	$.zhh.treeparse.defaults = OPTIONS;
}(jQuery);



/**
 * -----------------------------------------------------------
 * 遍历树节点
 * $.zhh.eachTree(nodes, filter, fn);
 * nodes = [json], 待遍历的根节点, required
 * filter = json, 过滤条件, optional
 * fn = function(node, parents), 回调函数, required
 * -----------------------------------------------------------
 * 查找树节点(找到第一个就返回)
 * $.zhh.eachFindNode(nodes, filter, deep);
 * deep = boolean 是否查找子节点, optional, 默认为true, 如果=false则只在根节点中查找
 * -----------------------------------------------------------
 * 查找所有树节点
 * $.zhh.eachFindNodes(nodes, filter, deep);
 * -----------------------------------------------------------
 * author: 赵卉华 / 2018-12-05
 * -----------------------------------------------------------
 */
+function($) {
	if (!$.zhh) { $.zhh = {}; }

	// 遍历树节点
	// nodes = [json], 待遍历的根节点, required
	// filter = json, 过滤条件, optional
	// fn = function(node, parents), 回调函数, required
	$.zhh.eachTree = function(nodes, filter, fn) {
		if ($.isFunction(filter)) { fn = filter; filter = undefined; }
		doEachTree(nodes, [], filter, true, fn);
	};
	$.zhh.eachFindNode = function(nodes, filter, deep) {
		if ($.dt.isBoolean(filter)) { deep = filter; filter = undefined; }
		var result = null;
		doEachTree(nodes, [], filter, deep, function(node) {
			result = node;
			return false;
		});
		return result;
	};
	$.zhh.eachFindNodes = function(nodes, filter, deep) {
		if ($.dt.isBoolean(filter)) { deep = filter; filter = undefined; }
		var result = [];
		doEachTree(nodes, [], filter, deep, function(node) {
			result.push(node);
		});
		return result;
	};
	var doEachTree = function(nodes, parents, filter, deep, fn) {
		$.each(nodes, function() {
			if (filter == null || matches(this, filter)) {
				var result = fn(this, parents);
				if (result === false) { return false; }
			}
			if (deep !== false && this.children && this.children.length > 0) {
				parents.push(this);
				doEachTree(this.children, parents, filter, deep, fn);
				parents.pop();
			}
		});
	};
	var matches = function(node, filter) {
		if (filter == null) { return true; }
		for (var key in filter) {
			var expect = filter[key];
			if ($.isArray(expect)) {
				if ($.inArray(node[key], expect) < 0) {
					return false;
				}
			} else {
				if (node[key] !== filter[key]) {
					return false;
				}
			}
		}
		return true;
	};
}(jQuery);



+function($) {
	/**
	 * -----------------------------------------------------------
	 * 事件监听和触发
	 * -----------------------------------------------------------
	 * 支付多个事件一起监听, 支持触发事件时传多个参数
	 * 先触发后注册也能得到回调
	 * -----------------------------------------------------------
	 * author: 赵卉华 / 2015-04-11
	 * -----------------------------------------------------------
		$.zhh.events.on("user.login", function(user) {
			...
		});
		// 多个事件一起监听
		$.zhh.events.on("user.login user.changed", function(newUser, oldUser) {
			...
		});
		
		var login = function() {
			...
			$.zhh.events.trigger("user.login", user);
			$.zhh.events.trigger(true, "user.once"); // true表示只触发一次
			...
		};
		
		var edit = function() {
			...
			// 多个参数
			$.zhh.events.trigger("user.edit", newUser, oldUser);
			$.zhh.events.trigger(true, "user.once"); // true表示只触发一次
			...
		};
	 * -----------------------------------------------------------
		var UserService = (function() {
			var event = $.zhh.event.create();
			var login = function(data) {
				...
				event.trigger("login", newUser);
				...
			};
			var edit = function(data) {
				...
				event.trigger("edit", newUser, oldUser);
				...
			};
			return {
				login:login, edit:edit,
				on:$.zhh.event.on(event),
				off:$.zhh.event.off(event)
			}
		})();
		UserService.on("login edit", function(newUser, oldUser) {
			console.log("user change");
			console.log(oldUser);
			console.log(newUser);
			...
		});
	 * -----------------------------------------------------------
	 */
	var getKeyName = function(key) {
		return "__ze_" + key + "__";
	};
	var Event = function() {
		this.listeners = {};
		this.triggered = {};
	};
	// 注册监听器
	Event.prototype.on = function(key, fn) {
		var self = this;
		$.each(key.split(/\s+/), function(i, key) {
			if (!key || typeof(fn) !== "function") {
				return;
			}
			// 注册监听器
			if (self.listeners[key]) {
				self.listeners[key].push(fn);
			} else {
				self.listeners[key] = [fn];
			}

			// 注册前已经触发过了, 直接回调
			var name = getKeyName(key);
			var triggered = self.triggered[key];
			if (triggered) {
				window.setTimeout(function() {
					fn.apply({ key:key, self:self, original:triggered.original }, triggered.args);
					/*
					try{
						fn.apply({ key:key, self:self, original:triggered.original }, triggered.args);
					} catch(e) {
						$.log.debug("event trigger error, key=" + key);
						$.log.debug(self);
						$.log.debug(e);
					}
					*/
				}, 0);
			}
		});
	};
	// 根据node清除缓存及监听
	Event.prototype.clear = function(node) {
		var self = this;
		var keys = [];
		for (var key in self.triggered) {
			var triggered = self.triggered[key];
			if (triggered.node == null) { continue; }
			if (triggered.node == node || $(node).find(triggered.node).length > 0) {
				delete self.listeners[key];
				for (var k in triggered) {
					delete triggered[k]; // 彻底删除缓存的节点
				}
				delete self.triggered[key];
			}
		}
	};
	// 移除监听器
	Event.prototype.off = function(key, fn) {
		var self = this;
		$.each(key.split(/\s+/), function(i, key) {
			if (!self.listeners[key]) { return true; }
			if (!fn) {
				delete self.listeners[key];
				var name = getKeyName(key);
				var triggered = self.triggered[key];
				if (triggered) {
					for (var k in triggered) {
						delete triggered[k]; // 彻底删除缓存的节点
					}
				}
				delete self.triggered[key];
			} else {
				var list = self.listeners[key];
				var index = $.inArray(fn, list);
				while (index >= 0) {
					list.splice(index, 1);
					index = $.inArray(fn, list);
				}
			}
		});
	};
	// 触发事件
	// $.zhh.events.trigger("xxx", args); // 先触发后注册不会得到回调
	// $.zhh.events.trigger(true, "ready", args);
	// true, 某些事件如init,ready之类, 只会触发一次, 这类事件先触发后注册也会得到回调
	// $.zhh.events.trigger(node, "init", args);
	// node, 同上, 针对每个node只会触发一次, 先触发后注册也会得到回调
	Event.prototype.trigger = function() {
		var index, node, key;
		if (typeof(arguments[0]) == "string") {
			node = false; key = arguments[0]; index = 1;
		} else {
			node = arguments[0]; key = arguments[1]; index = 2;
		}
		// 在IE8有问题, args始终等于空数组
		// var args = Array.prototype.splice.call(arguments, index);
		var args = [];
		for (var i = index; i < arguments.length; i++) {
			args.push(arguments[i]);
		}

		var self = this;
		var name = getKeyName(key);
		var original = typeof(node) == "boolean" ? undefined : node;
		// 如果once事件已经触发过了就不再触发
		if (self.triggered[key]) {
			if (node == true) { return; }
			else if (node[name]) { return; }
		}
		window.setTimeout(function() {
			// 记录已经触发的参数
			// 用于init,ready之类只会触发一次的事件, 先触发后注册也能得到回调
			if (node) { 
				self.triggered[key] = { key:key, args:args, node:node, original:original };
				if (typeof(node) != "boolean") { node[name] = true; }
			}
			// 逐一回调监听器
			var list = self.listeners[key] || [];
			// 修改BUG: 
			// 如果在回调函数中又调用了event.on(), 则会出现多调用一次回调的问题
			// for (var i = 0; i < list.length; i++)
			for (var i = 0, length = list.length; i < length; i++) {
				list[i].apply({ key:key, self:self, original:original }, args);
				/*
				try {
					list[i].apply({ key:key, self:self, original:original }, args);
				} catch(e) {
					$.log.debug("event trigger error, key=" + key);
					$.log.debug(self);
					$.log.debug(e);
				}
				*/
			}
		}, 0);
	};

	if (!$.zhh) { $.zhh = {}; }
	$.zhh.events = new Event();
	$.zhh.event = {
		create:function() { 
			return new Event();
		},
		on:function(e) { 
			return function(key, fn) { e.on(key, fn); };
		},
		off:function(e) { 
			return function(key, fn) { e.off(key, fn); };
		}
	};
}(jQuery);

/**
 * -------------------------------------------------------------
 * 判断数据类型
 * isNull isUndefined
 * isNumeric isNumber isBoolean isString isDate isRegExp isError
 * isObject isPlainObject isEmptyObject isFunction isArray isWindow
 * isElement isDomElement isJqueryElement
 * -------------------------------------------------------------
 * $.dt.isNumber("0") = false
 * $.dt.isNumeric("0") = true
 * $.dt.isNumeric("0xFF") = true
 * $.dt.isObject(document) = true
 * $.dt.isPlainObject(document) = false
 * $.dt.isDomElement(document) = true
 * $.dt.isJqueryElement($(document)) = true
 * $.dt.isElement(document) = true
 * $.dt.isElement($(document)) = true
 * -----------------------------------------------------------
 * author: 赵卉华 / 2016-10-15
 * -----------------------------------------------------------
 */
+function($) {
	if (!$.dt) { $.dt = {}; }
	$.each("isFunction isArray isWindow isEmptyObject isPlainObject isNumeric".split(/\s+/g), function(i, name) {
		$.dt[name] = $[name];
	});
	$.each("Null Undefined Boolean Number String Date RegExp Object Error".split(/\s+/g), function(i, name) {
		$.dt["is" + name] = function(object) { return $.type(object) == name.toLowerCase(); };
	});
	$.dt.isDomElement = function(object) {
		// object.nodeType, 判断DOMElement, jquery就是这么判断的
		return $.dt.isObject(object) && !$.dt.isPlainObject(object) && !!object.nodeType;
	};
	$.dt.isJqueryElement = function(object) {
		return object instanceof $ && $.dt.isDomElement(object[0]);
	};
	$.dt.isElement = function(object) {
		return object instanceof $ && $.dt.isDomElement(object[0]) || $.dt.isDomElement(object);
	};
}(jQuery);


/**
 * -------------------------------------------------------------
 * 解析参数
 * $.zhh.parseArgs(args, fields, sequential, parser);
 * args 参数列表, fields 字段列表, sequential=true|false 强制顺序, parser=function 解析器函数
 * -------------------------------------------------------------
 * 如: dosomething(type, url, data, fn);
 * var vars = $.zhh.parseArgs(arguments, "type, url, data, fn", function(field, value) { ... });
 * dosomething("POST", "xx.do", "x=xxx&y=yyy", callback) --> vars = { type:"POST", url:"xx.do", data:"x=xxx&y=yyy", fn:callback };
 * dosomething("POST", "xx.do", callback) --> vars = { type:"POST", url:"xx.do", fn:callback };
 * -------------------------------------------------------------
	var vars = $.zhh.parseArgs(arguments, "type, url, data, fn", function(field, value) {
		if ($.isFunction(value)) {
			return "fn";
		} else if ($.isPlainObject(value)) {
			return "data";
		} else if (typeof(value) == "string") {
			// dosomething("POST", "xx.do", "x=xxx&y=yyy", callback); // type=POST, url=xx.do, data=x=xxx&y=yyy
			// dosomething("1", "2", callback); // type=1, url=2, data=undefined
			// dosomething("1", callback); // type=1, url=undefined, data=undefined
			if (!this.type) {
				return "type";
			} else if (!this.url) {
				return "url";
			} else if (!this.data) {
				return "data";
			}
		} else if (value.nodeType || value instanceof jQuery) {
			return { field:"data", value:$(value).serializeJson() };
		}
	};
	var vars = $.zhh.parseArgs(arguments, "data, loading, fn", function(field, value) {
		if ($.isFunction(value)) {
			return "fn";
		} else if (typeof(value) == "boolean") {
			return "loading";
		} else if (typeof(value) == "string" || $.isPlainObject(value)) {
			return "data";
		} else if (value.nodeType || value instanceof jQuery) { // $("form")
			return { field:"data", value:$(value).serializeJson() };
		}
	};
 * -----------------------------------------------------------
 * author: 赵卉华 / 2016-10-13
 * -----------------------------------------------------------
 */
+function($) {
	if (!$.zhh) { $.zhh = {}; }
	$.zhh.parseArgs = function(args, fields, sequential, parser) {
		if (typeof(fields) == "string") {
			fields = fields.split(/\s*[ ,\|]\s*/i);
		}
		if (typeof(sequential) != "boolean") {
			parser = sequential; sequential = true;
		}
		var vars = {};
		var offset = 0;
		for (var i = 0; i < fields.length; ) {
			var field = fields[i];
			var value;
			if (field == "this") {
				// 函数直接调用而不是.call时, this==jQuery 或 window
				value = (this === $ || $.dt.isWindow(this)) ? undefined : this;
				offset ++;
			} else {
				value = args[i - offset];
			}
			// result = { field:"string", value:value, copy:json }
			// result = "string" 转换为 { field:"string", value:value }
			var result = parser.call(vars, field, value);
			if (result && $.dt.isString(result)) {
				result = { field:result, value:value };
			}
			if (!$.isPlainObject(result)) {
				i++;
				continue;
			} else if (!sequential) {
				i++;
			} else {
				var index = $.inArray(result.field, fields);
				if (index < 0) {
					i++;
				} else if (index < i) {
					throw new Error("The forced sequential model can not be back.");
				} else {
					i = index + 1;
				}
			}

			if (vars[result.field] === undefined && result.field != undefined && result.field != "this" && result.value !== undefined) {
				vars[result.field] = result.value;
			}
			if ($.isPlainObject(result.copy)) {
				$.extend(true, vars, result.copy);
			}
		}
		return vars;
	};
}(jQuery);


/**
 * -------------------------------------------------------------
 * 过滤筛选, keys=list或逗号分隔的字符串
 * $.zhh.filterOptions(json, keys)
 * -------------------------------------------------------------
 * 1. 将字符串解析为JSON选项
 * $.zhh.parseOptions(string)
 * -------------------------------------------------------------
 * 2. 将普通选项解析为主体+列表选项
 * $.zhh.parseOptions(options, listKey)
 * options:josn or json list 选项, listKey:string 列表的KEY
 * 解析后的选项有两部分, 主体选项和列表选项, 
 * 如: { label:'xxx',disabled:false, rules:[{required:true},{regexp:'ascii'}] }
 * label,disabled是主体选项, rules是列表选项
 * -------------------------------------------------------------
 * 如: listKey = "rules"
 * 标准格式不作转换: 
 * options = { label:'xxx',disabled:false, rules:[{required:true},{regexp:'ascii'},{regexp:'illegal-char'}] }
 * 没有主体选项:
 * 转换前: options = [ {required:true},{regexp:'ascii'},{regexp:'illegal-char'} ]
 * 转换后: options = { rules:[{required:true},{regexp:'ascii'},{regexp:'illegal-char'}] }
 * 列表选项第一项作为主体选项, 以{$:{}}格式配置
 * 转换前: options = [ {$:{label:'xxx',disabled:false}}, {required:true},{regexp:'ascii'},{regexp:'illegal-char'} ]
 * 转换后: options = { label:'xxx',disabled:false, rules:[{required:true},{regexp:'ascii'},{regexp:'illegal-char'}] }
 * 只有主体选项:
 * 转换前: options = {$:{label:'xxx',disabled:false}}
 * 转换后: options = { label:'xxx',disabled:false }
 * 只有一项列表选项:
 * 转换前: options = { required:true,regexp:'illegal-char' }
 * 转换后: options = { rules:[{required:true,regexp:'illegal-char'}] }
			// data-vld="[ { label:'xxx',target:'input[name=xxx]', rules:[{required:true},{regexp:'ascii'},{regexp:'illegal-char'},...] } ]"
			// data-vld="{ tips:true,disabled:false, targets:[ { label:'xxx',target:'input[name=xxx]', rules:[{required:true},{regexp:'ascii'},{regexp:'illegal-char'},...] } ] }"
			// data-vld="[ {$:{tips:true,concat:false,disabled:false}}, [ {$:{label:'xxx',target:'input[name=xxx]'}}, {required:true},{regexp:'ascii'},{regexp:'illegal-char'},... ] ]"
 * -----------------------------------------------------------
 * author: 赵卉华 / 2016-10-19
 * -----------------------------------------------------------
 */
+function($) {
	if (!$.zhh) { $.zhh = {}; }
	$.zhh.filterOptions = function(options, fields) {
		var array = $.isArray(fields) ? fields : fields.split(/\s*,\s*/g);
		var map = {};
		$.each(array, function(i, field) {
			map[field] = options[field];
		});
		return map;
	};
	$.zhh.parseOptions = function(options, listKey) {
        if (options == null || options == "") { return {}; }

        // 1. 将字符串解析为JSON选项
        if (!$.isArray(options) && !$.isPlainObject(options)) {
        	options = $.trim(options);
        	if (/^\w+$/.test(options)) {
        		if (options === "true") { options = true; }
        		else if (options === "false") { options = false; }
        		else if ($.dt.isNumeric(options)) { options = options * 1; }
        		// else { options = options; }
        	} else {
                if (!options.startsWith("{") && !options.startsWith("[")) {
                	options = "{" + options + "}";
                }
                options = (new Function("return " + options))();
        	}
        }
        if (!listKey) { return options; }

        // 2. 将普通选项解析为主体+列表选项
		if ($.isArray(options)) {
			var o = { };
			var list = [];
			$.each(options, function(i, option) {
				if (i == 0 && "$" in option) {
					var temp = parseMainOptions(o, option);
					if (!$.isEmptyObject(temp)) { list.push(temp); }
				} else {
					list.push(option);
				}
			});
			if (list.length) { o[listKey] = list; }
			return o;
		} else if ($.isPlainObject(options)) {
			if ($.isEmptyObject(options)) {
				return {};
			} else if ("$" in options) {
				var o = {};
				options = parseMainOptions(o, options);
				if (!$.isEmptyObject(options)) { o[listKey] = [options]; }
				return o;
			} else if ($.isPlainObject(options[listKey])) {
				options[listKey] = [options[listKey]]
				return options;
			} else if ($.isArray(options[listKey])) {
				return options;
			} else {
				var o = {};
				o[listKey] = [options];
				return o;
			}
		} else {
			var o = {};
			o[listKey] = [options];
			return o;
		}
	};
	var parseMainOptions = function(options, option) {
		var temp = $.extend(true, {}, option);
		if ($.isPlainObject(option.$)) { // {$:{xxx:true,yyy:false}}
			$.extend(true, options, temp.$);
			delete temp.$;
		} else { // {$:true,xxx:true,yyy:false} 或 {$:0,xxx:true,yyy:false}
			delete temp.$;
			$.extend(true, options, temp);
		}
		return temp;
	};
}(jQuery);



/**
 * -------------------------------------------------------------
 * 生成随机数
 * -------------------------------------------------------------
 * Randoms.number(4); nnnn(string)
 * Randoms.number(100, 200); 100~200(number)
 * Randoms.uuid(); // UUID
 * Randoms.timeindex(); // 时间+随机数
 * -------------------------------------------------------------
 * author: 赵卉华 / 2016-10-19
 * -----------------------------------------------------------
 */
var Randoms = (function() {
	var number = function(min, max) {
		if (max == null) {
			var list = [];
			for(var i = 0; i < Math.min(min, 20) ; i ++) {
			    list.push(Math.floor(Math.random()*10));
			}
			return list.join("");
		} else {
			return Math.floor(Math.random()*(max-min+1)+min);
		}
	};
	var uuid = function(len, radix) {
		var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
		var uuid = [], i;
		radix = radix || chars.length;
		if (len) {
			// Compact form
			for (i = 0; i < len; i++) {
				uuid[i] = chars[0 | Math.random()*radix];
			}
		} else {
			// rfc4122, version 4 form
			var r;
			// rfc4122 requires these characters
			uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
			uuid[14] = '4';

			// Fill in random data. At i==19 set the high bits of clock sequence
			// as
			// per rfc4122, sec. 4.1.5
			for (i = 0; i < 36; i++) {
				if (!uuid[i]) {
					r = 0 | Math.random()*16;
					uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r];
				}
			}
		}

		return uuid.join('');
	};
	var timeindex = function() {
		return Dates.format(new Date(), "yyMMddHHmmssSSS") + number(10);
	};
	return { number:number, uuid:uuid, timeindex:timeindex };
})();

/**
 * -------------------------------------------------------------
 * 日期格式化
 * -------------------------------------------------------------
 * y: 表示年
 * M：表示一年中的月份 1~12
 * d: 表示月份中的天数 1~31
 * H：表示一天中的小时数 0~23
 * m: 表示小时中的分钟数 0~59
 * s: 表示分钟中的秒数   0~59
 * S: 表示秒中的毫秒数   0~999
 * -------------------------------------------------------------
 * Dates.format(new Date());
 * Dates.format(new Date(), Dates.FORMAT.DATE);
 * Dates.format(new Date(), Dates.FORMAT.DATETIME);
 * Dates.format(new Date(), 'yyyy-MM-dd HH:mm:ss.SSS');
 * -------------------------------------------------------------
 * Dates.parse("10-20");
 * Dates.parse("5月6日");
 * Dates.parse("2000-10-20");
 * Dates.parse("2000-10-20 15:25:35");
 * Dates.parse("2000-10-20 15:25:35.888");
 * -------------------------------------------------------------
 * author: 赵卉华 / 2014-10-20
 * -------------------------------------------------------------
 */
var Dates = (function(){
	var FORMAT = {
		DATE: 'yyyy-MM-dd',
		TIME: 'HH:mm:ss',
		DATETIME: 'yyyy-MM-dd HH:mm:ss'
	};
	
	/**  
	 * 根据给定的日期得到日期的月,日,时,分和秒的对象
	 * @params date 给定的日期 date为非Date类型, 则获取当前日期
	 * @return 有给定日期的月、日、时、分和秒组成的对象
	 */
	var getDateObject = function(date) {
		if (!$.dt.isDate(date)) {
			date = new Date();
		}
		return {
			'M+' : date.getMonth() + 1,
			'd+' : date.getDate(),
			'H+' : date.getHours(),
			'm+' : date.getMinutes(),
			's+' : date.getSeconds(),
			'S+' : date.getMilliseconds()
		};
	};

	/**  
	 * 根据给定的日期时间格式, 格式化当前日期
	 * @params string 格式化字符串, 如："yyyy-MM-dd", 默认格式为："yyyy-MM-dd HH:mm:ss"
	 * @return 返回根据给定格式的字符串表示的时间日期格式
	 */
	var format = function(date, string) {
		try {
			if (!$.dt.isDate(date)) { date = parse(date); }
			var temp = string || FORMAT.DATETIME;
			var fullYear = String(date.getFullYear());
			temp = temp.replace(/y+/, function(word) {
				return word.length > 2 ? fullYear : fullYear.substr(-2);
			});
			var dates = getDateObject(date);
			for (var i in dates) {
				var regexp = new RegExp(i, 'g');
				temp = temp.replace(regexp, function(word) {
					var target = String(dates[i]);
					if (word.length > target.length) {
						return ('0000000000' + target).substr(-word.length);
					} else {
						return target;
					}
				});
			}
			return temp;
		} catch (e) {
			$.log.error(e);
		}
	};

	var parse = function(date, defvalue) {
		if (!date) {
			return defvalue;
		} else if ($.dt.isNumeric(date)) {
			return new Date(date * 1);
		} else if ($.dt.isDate(date)) {
			return new Date(date.getTime());
		} else if (typeof(date) == "string") {
			var m = /^(\d{1,2})[\-\/](\d{1,2})$/.exec(date);
			if (m) {
				return new Date(new Date().getFullYear(), parseInt(m[1]-1), parseInt(m[2]));
			} else {
				var m = /^(\d{1,2})月(\d{1,2})日$/.exec(date);
				if (m) {
					return new Date(new Date().getFullYear(), parseInt(m[1]-1), parseInt(m[2]));
				} else {
					var m = /^(\d{4})[\-\/](\d{2})[\-\/](\d{2})$/.exec(date);
					if (m) {
						return new Date(parseInt(m[1]), parseInt(m[2]-1), parseInt(m[3]));
					} else {
						var m = /^(\d{4})[\-\/](\d{2})[\-\/](\d{2}) (\d{2}):(\d{2}):(\d{2})(?:\.(\d{3}))?$/.exec(date);
						if (m) {
							return new Date(parseInt(m[1]), parseInt(m[2]-1), parseInt(m[3]), parseInt(m[4]), parseInt(m[5]), parseInt(m[6]), parseInt(m[7]||0));
						} else {
							// JSON.stringify(new Date()); -- 2016-12-07T11:56:24.920Z
							// 输出的是ISO时间, 有时区差, 需要用new Date()来还原
							var m = /^(\d{4})[\-\/](\d{2})[\-\/](\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d{3}))?Z$/i.exec(date);
							if (m) {
								return new Date(date);
							} else {
								// safari不支持yyyy-mm-dd格式
								// chrome/ff/ie/safari都支持yyyy/mm/dd
								var temp = new Date(date.replace(/-/g, '/'));
								return isNaN(temp) ? defvalue : temp;
							}
						}
					}
				}
			}
		} else {
			return defvalue;
		}
	};

	/**
	 * -------------------------------------------------------------
	 * 日期转换为易识别的字符串
	 * @param showTime = 是否显示时间, 'auto'=只有今天显示时间
	 * @param useJust = 是否使用'刚刚'
	 * -------------------------------------------------------------
	 * 如果当前时间是2014-12-12 12:34:56
	 * Dates.toReadable(Dates.calculate(new Date(),'-2m')); = 刚刚
	 * Dates.toReadable(Dates.calculate(new Date(),'-1H')); = 11:34
	 * Dates.toReadable(Dates.calculate(new Date(),'+1H'), 'auto'); = 13:34
	 * Dates.toReadable(Dates.calculate(new Date(),'-2H'), false); = 今天
	 * Dates.toReadable(Dates.calculate(new Date(),'-1d')); = 昨天
	 * Dates.toReadable(Dates.calculate(new Date(),'-2d')); = 前天
	 * Dates.toReadable(Dates.calculate(new Date(),'+1d')); = 明天
	 * Dates.toReadable(Dates.calculate(new Date(),'+2d')); = 后天
	 * Dates.toReadable(Dates.calculate(new Date(),'-3d')); = 12月9日
	 * Dates.toReadable(Dates.calculate(new Date(),'+8d')); = 12月20日
	 * Dates.toReadable(Dates.calculate(new Date(),'-1y')); = 13年12月12日
	 * -------------------------------------------------------------
	 * Dates.toReadable(Dates.calculate(new Date(),'-2m'), true); = 刚刚
	 * Dates.toReadable(Dates.calculate(new Date(),'-2m'), true, false); = 今天12:32
	 * Dates.toReadable(Dates.calculate(new Date(),'-1H'), true); = 今天11:34
	 * Dates.toReadable(Dates.calculate(new Date(),'+1H'), true); = 今天13:34
	 * Dates.toReadable(Dates.calculate(new Date(),'-1d'), true); = 昨天12:34
	 * Dates.toReadable(Dates.calculate(new Date(),'-2d'), true); = 前天12:34
	 * Dates.toReadable(Dates.calculate(new Date(),'+1d'), true); = 明天12:34
	 * Dates.toReadable(Dates.calculate(new Date(),'+2d'), true); = 后天12:34
	 * Dates.toReadable(Dates.calculate(new Date(),'-3d'), true); = 12月9日12:34
	 * Dates.toReadable(Dates.calculate(new Date(),'+8d'), true); = 12月20日12:34
	 * Dates.toReadable(Dates.calculate(new Date(),'-1y'), true); = 13年12月12日12:34
	 * -------------------------------------------------------------
	 * author: 赵卉华 / 2014-12-12
	 * -------------------------------------------------------------
	 */
	var DAY = 24 * 60 * 60 * 1000;
	var DATES = { '-2':'前天', '-1':'昨天', '0':'今天', '1':'明天', '2':'后天' };
	var toReadable = function(date, showTime, useJust) {
		if (showTime !== true && showTime !== false) { showTime = 'auto'; }
		var d = Dates.parse(date);
		if (isNaN(d)) { return ""; }
		var now = new Date();
		var offset = Math.floor(d.getTime() / DAY) - Math.floor(now.getTime() / DAY);
		if (useJust != false && now.getTime() > d.getTime() && now.getTime() - d.getTime() < 5 * 60 * 1000) {
			return "刚刚"; // 5分钟以内
		} else if (Math.floor(offset) == 0) {
			if (showTime === false) {
				return DATES[0]; // 今天
			} else if (showTime === true) {
				return DATES[0] + Dates.format(d, 'HH:mm');
			} else {
				return Dates.format(d, 'HH:mm');
			}
		} else {
			if (offset in DATES) {
				return DATES[offset] + (showTime === true ? Dates.format(d, 'HH:mm') : '');
			} else if (d.getFullYear() != now.getFullYear()) {
				return Dates.format(d, showTime === true ? 'yy年M月d日HH:mm' : 'yy年M月d日'); // 跨年了
			} else {
				return Dates.format(d, showTime === true ? 'M月d日HH:mm' : 'M月d日');
			}
		}
	};

	/**
	 * -------------------------------------------------------------
	 * 转换为持续时间字符串
	 * -------------------------------------------------------------
	 * Dates.toDuration(begin:Date|String, [end:Date|String], [useMillisecond:boolean])
	 * Dates.toDuration(time:int, [useMillisecond:boolean])
	 * -------------------------------------------------------------
	 * 8000=00:00:08
	 * 488000=00:08:08
	 * 11288000=03:08:08
	 * 97688000=1天03:08:08
	 * 31536000000=365天00:00:00
	 * -------------------------------------------------------------
	 */
	var toDuration = function(begin, end, useMillisecond) {
		var time;
		if ($.dt.isNumeric(begin)) { // (time:int, [useMillisecond:boolean])
			time = begin * 1; useMillisecond = end;
		} else { // (begin:Date|String, [end:Date|String], [useMillisecond:boolean])
			begin = parse(begin);
			if ($.dt.isDate(end)) {  }
			else if ($.dt.isString(end)) { end = parse(end); }
			else { useMillisecond = end; end = new Date(); }
			time = end.getTime() - begin.getTime();
		}

        var pattern = useMillisecond ? "HH:mm:ss.SSS" : "HH:mm:ss";
		var offset = new Date().getTimezoneOffset() * 60 * 1000;
		var string = format(new Date(time + offset), pattern); // 0时区时间
		if (time >= DAY) {
			string = (time / DAY).toFixed(0) + "天" + string;
		}
		return string;
	};

	/**
	 * -------------------------------------------------------------
	 * 获取本周第一天(星期一)和最后一天(星期天)
	 * Dates.getFirstDayOfWeek("2016-03-01");  = 2016-02-29
	 * Dates.getLastDayOfWeek ("2016-03-01");  = 2016-03-06
	 * -------------------------------------------------------------
	 * 获取本月第一天和最后一天
	 * Dates.getFirstDayOfMonth("2016-02-20"); = 2016-02-01
	 * Dates.getLastDayOfMonth ("2016-02-20"); = 2016-02-29
	 * -------------------------------------------------------------
	 * author: 赵卉华 / 2016-12-07
	 * -------------------------------------------------------------
	 */
	var getFirstDayOfWeek = function(date) {
		var d = parse(date, new Date());
		var index  = d.getDay() || 7;
		d.setDate(d.getDate() + 1 - index);
		return new Date(d.getFullYear(), d.getMonth(), d.getDate());
	};
	var getLastDayOfWeek = function(date) {
		var d = parse(date, new Date());
		var index  = d.getDay() || 7;
		d.setDate(d.getDate() + 7 - index);
		return new Date(d.getFullYear(), d.getMonth(), d.getDate());
	};
	var getFirstDayOfMonth = function(date) {
		var d = parse(date, new Date());
		return new Date(d.getFullYear(), d.getMonth(), 1);
	};
	var getLastDayOfMonth = function(date) {
		var d = parse(date, new Date());
		// 下一个月的第一天的前一天, 即本月最后一天
		return new Date(d.getFullYear(), d.getMonth() + 1, 0);
	};

	/**
	 * -------------------------------------------------------------
	 * 计算相对日期
	 * Dates.calculate("2016-03-01", "-1d");  = 2016-02-29
	 * Dates.calculate("2016-03-01", "-2M");  = 2016-01-01
	 * Dates.calculate("2016-03-01", "-3y");  = 2013-03-01
	 * Dates.calculate("2016-03-01", "+2d");  = 2016-03-03
	 * Dates.calculate("2016-03-01", "-2M+3d");  = 2016-01-04
	 * -------------------------------------------------------------
	 * author: 赵卉华 / 2016-12-08
	 * -------------------------------------------------------------
	 */
	var calculate = function(date, option) {
		var d = parse(date, new Date());
		if ($.dt.isNumeric(option)) {
			d.setDate(d.getDate() + option * 1);
			return d;
		} else if ($.dt.isString(option)) {
			// "-3d"/"-2M"/"+1y"之类的相对日期
			var ptn = /([\+\-]?\d+)\s*([a-zA-Z]+)/g;
			for (var i = 0, matcher = null; true;)  {
				matcher = ptn.exec(option);
				if (!matcher) {
					var temp = option.substring(i);
					if (temp && !/\s*/.test(temp)) {
						$.log.error("RelativeDate option '" + option + "' format error!")
						return undefined;
					}
					break;
				}
				var index = matcher.index;
				var temp = option.substring(i, index);
				if (temp && !/\s*/.test(temp)) {
					$.log.error("RelativeDate option '" + option + "' format error!")
					return undefined;
				}
				var number = matcher[1] * 1;
				var type = matcher[2];
				switch(type) {
					case 'y':
					case 'Y':
						d.setFullYear(d.getFullYear() + number);
						break;
					case 'M':
						d.setMonth(d.getMonth() + number);
						break;
					case 'd':
						d.setDate(d.getDate() + number);
						break;
					case 'h':
					case 'H':
						d.setHours(d.getHours() + number);
						break;
					case 'm':
						d.setMinutes(d.getMinutes() + number);
						break;
					case 's':
						d.setSeconds(d.getSeconds() + number);
						break;
					case 'S':
						d.setMilliseconds(d.getMilliseconds() + number);
						break;
					default:
						$.log.error("RelativeDate option type '" + type + "' error!")
						return undefined;
				}
				i = index + matcher[0].length;
			}
			return d;
		}
		$.log.error("RelativeDate option format error!")
		return undefined;
	};
	return {
		FORMAT:FORMAT, format:format, parse:parse, 
		toReadable:toReadable, toDuration:toDuration, calculate:calculate,
		getFirstDayOfWeek:getFirstDayOfWeek, getLastDayOfWeek:getLastDayOfWeek,
		getFirstDayOfMonth:getFirstDayOfMonth, getLastDayOfMonth:getLastDayOfMonth
	};
})();







+function($) {
	/**
	 * -------------------------------------------------------------<br>
	 * 解析data-options<br>
	 * -------------------------------------------------------------<br>
	 * $(".xxx").zoptions() --> get "options"
	 * $(".xxx").zoptions(options) --> set "options"
	 * $(".xxx").zoptions("key") --> get "key"
	 * $(".xxx").zoptions("key", options) --> set "key"
	 * -------------------------------------------------------------<br>
	 * <div class="xxx" data-options="text:'baidu', url:'http://baidu.com'"></div><br>
	 * 获取options<br>
	 * $(".xxx").zoptions() --> { text:'baidu', url:'http://baidu.com' }<br>
	 * 设置options<br>
	 * $(".xxx").zoptions({ id:1, callback:fn })<br>
	 * $(".xxx").zoptions() --> { text:'baidu', url:'http://baidu.com', id:1, callback:fn }<br>
	 * -------------------------------------------------------------<br>
	 * @author 赵卉华<br>
	 * date: 2016-08-18<br>
	 * -------------------------------------------------------------<br>
	 */
	$.fn.zoptions = function(key, resetOptions) {
		if (key == undefined || $.isPlainObject(key)) {
			resetOptions = key;
			key = "options";
		}
		if (resetOptions !== undefined) {
			return this.each(function() {
				$.data(this, key, resetOptions);
				var stringOptions = resetOptions;
				if ($.isPlainObject(resetOptions) || $.isArray(resetOptions)) {
					stringOptions = $.zhh.toJsonString(resetOptions);
				}
				$(this).attr("data-" + key, stringOptions);
			});
		} else {
			var me = $(this);
			var options = $.data(me, key) || me.attr("data-" + key);
			if (!options) {
				options = {};
				$.data(me, key, options);
			} else if (typeof(options) == "string") {
				options = $.zhh.parseOptions(options);
				// me.data("options", {})将会清空data-options属性, 导致[data-options]选择符找不到节点
				$.data(me, key, options);
			}
			return options;
		}
	};
}(jQuery);



/**
 * -----------------------------------------------------------
 * 不可编辑
 * -----------------------------------------------------------
 * <input type="text" class="datepicker uneditable" />
 * $("input.uneditable").uneditable(true);
 * -----------------------------------------------------------
 * author: 赵卉华 / 2016-09-26
 * -----------------------------------------------------------
 */
+function($) {
	var key = "__uneditable__";
	var uneditable = function (e) {
		if (e.keyCode != 9 && e.keyCode != 13) { 
			e.preventDefault();
		}
	};
	$.fn.uneditable = function(enable) {
		if (enable == undefined) {
			return this.data(key);
		} else {
			return this.each(function() {
				var me = $(this);
				var older = me.data(key);
				if (enable && older == undefined) {
					me.bind("keydown.uneditable", uneditable).data("uneditable", true);
				} else if (!enable && older != undefined) {
					me.unbind("keydown.uneditable", uneditable).removeData("uneditable");
				}
			});
		}
	};
}(jQuery);


+function($) {
	/**
	 * -------------------------------------------------------------
	 * 增强AJAX请求, loading, 防重复提交, 支持复杂对象参数, 表单校验
	 * 提交后检查返回码并提示错误, 只有成功才回调
	 * -------------------------------------------------------------
	 * var data = {
	 *     "name":"某公司",
	 *     "address":{"city":"南京"},
	 *     "users":[
	 *         {"name":"张三","addresses":[{"city":"南京"}]},
	 *         {"name":"李四","addresses":[{"city":"合肥"},{"city":"南京"}]}
	 *     ]
	 * };
	 * -------------------------------------------------------------
	 * $.zajax(url, [data], [loading], [callback|options]);
	 * $.zajax(form, [url], [data], [loading], [callback|options]);
	 * -------------------------------------------------------------
	 * $(button).zajax(url, [data], [loading], [callback|options]);
	 * $(button).zajax(form, [url], [data], [loading], [callback|options]);
	 * -------------------------------------------------------------
	 * 参数说明:
		button: 用于防止重复提交, 和查找form
		url: string|{GET:url}|{POST:url}, 如果是string, GET|POST由配置项决定: $.zajax.defaults.options.type
		form: DomElement|jQuery对象, 需要提交的表单, 如果url为空, 则url=form.action,GET|POST=form.method
		data: string|json, 需要提交的数据, 如果有form又有data, 则合并到一起提交
		loading: true|false, 是否显示正在加载
		callback: function, 成功时的回调函数
		options: { // 选项
			O | OPTIONS: true, // 选项标志, 用于区分options和data, 如果有succ或fail则不需要填写
			succ: function(json), // 成功回调函数
			fail: function(json), // 失败回调函数
			type: "POST",
			dataType: "json",
			loading: true | { show:true, stop:true }, // 是否显示正在加载
			validate: true, // 是否执行表单校验
			readForm: true, // 是否从form中读取请求数据, 如果button在form中, 也会读取
			check: true, // 是否检查返回码
			prepare: true, // 请求前是否执行预处理
			finish: true, // 是否执行请求完成后的处理函数
			succTips: true, // 是否显示成功提示
			failTips: true // 是否显示失败提示
		}
	 * -------------------------------------------------------------
	 * @author 赵卉华  / 2016-08-18 / 2016-10-13 
	 * -------------------------------------------------------------
	 */
	$.fn.zajax = function() {
		var args = arguments;
		return this.each(function() {
			zajax.apply(this, args);
		});
	};

	$.zajax = function(url, data, loading, fn) {
		zajax.apply(this, arguments);
	};

	var o = $.zajax.defaults = {
		fn: {
			loading: { show:undefined, stop:undefined }, // 显示正在加载的函数
			readForm: undefined, // 从form中读取请求数据, 默认实现涉及form和data合并, 比较长, 写在下面
			validate: undefined, // 表单校验
			prehandle: undefined, // 表单校验后的异步处理函数
			upload: undefined, // 文件上传
			prepare: undefined, // 请求提交前的预处理函数
			finish: undefined, // 请求完成后的处理函数(无论成功失败都会调用), 在callback之前
			check: undefined, // 检查返回码的函数
			succTips: undefined,
			failTips: undefined
		},
		options: {
			succ: undefined, // 成功回调函数
			fail: undefined, // 失败回调函数
			type: "POST",
			dataType: "json",
			loading: true, // 是否显示正在加载
			readForm: true, // 是否从form中读取请求数据, 如果button在form中, 也会读取
			validate: true, // 是否执行表单校验
			prehandle: true, // 是否执行表单校验后的异步处理函数
			upload: true, // 是否执行文件上传
			prepare: true, // 请求前是否执行预处理
			finish: true, // 是否执行请求完成后的处理函数
			check: true, // 是否检查返回码
			succTips: true, // 是否显示成功提示
			failTips: true // 是否显示失败提示
		}
	};

	var zajax = function() {
		var self = this;

		// e: button, form, url, data, loading, succ, fail, ...
		var e = parseArgs.apply(this, arguments);
		var $elem = e.button || e.form || $([]);
		if ($elem.attr("data-submit-doing")) {
			// 防止重复提交
			// 不能用disabled
			// 1. <a>标签作为按钮时disabled不起作用
			// 2. disabled的按钮无法获取焦点, 导致button.msger()错误提示出现问题
			return;
		}

		var execute = function(finish, callback, showTips, result, json) {
			// 清除正在提交
			$elem.removeAttr("data-submit-doing");
			if ($.isFunction(e[finish])) {
				e[finish].call(e, result); // 请求完成后的处理函数
			}
			if ($.isFunction(e[callback])) {
				e[callback].call(e, json); // 回调
			}
			if ($.isFunction(e[showTips])) {
				e[showTips].call(e, json); // 提示
			}
		};
		var success = function(json) {
			if (!$.isFunction(e.check) || e.check(json)) {
				execute("finish", "succ", "succTips", true, json);
			} else {
				$.log.error(json);
				execute("finish", "fail", "failTips", false, json);
			}
		};
		var error = function(){
			$.log.error(arguments);
			execute("finish", "fail", "failTips", false);
		};
		var beforeSend = function() {
			$elem.attr("data-submit-doing", "true");
			if (e.loading && $.isFunction(e.loading.show)) {
				e.loading.show.call(e);
			}
		};
		var complete = function(){
			// 清除正在加载
			if (e.loading && $.isFunction(e.loading.stop)) {
				e.loading.stop.call(e);
			}
		};
		$.extend(e, { success:success, error:error, beforeSend:beforeSend, complete:complete });
		

		if (e.readForm && (e.button || e.form)) {
			// 如果有button而没有form, 自动查找form
			if (e.button && !e.form) {
				var form = e.button.closest("form");
				if (form.length) { e.form = form; }
			}

			// 如果有form而没有url, 则url=form.action,type=form.method
			if (e.form && !e.url) {
				e.url = e.form.attr("action");
				var method = e.form.attr("method");
				if (method) { e.type = method.toUpperCase(); }
			}
		}

		var uploadCallback = function() {
			// 从form中读取请求数据
			if ($.isFunction(e.readForm) && e.form) {
				e.readForm.call(e);
			}

			var prehandleCallback = function() {
				// 发送请求前修改参数的机会
				if ($.isFunction(e.prepare)) {
					e.prepare.call(e);
				}
				if (!e.url) {
					throw new Error("ajax url is undefined.");
				} else {
					e.url += (e.url.indexOf("?") < 0 ? "?" : "&") + "_=" + Randoms.number(8);
				}
				// .txt格式的用GET方式请求, 并用$.zhh.parseOptions解析(可以有注释,json的key不需要引号)
				if (e.url.replace(/\?.*$/, '').endsWith(".txt")) {
					e.method = "GET";
					e.converters = { "text json":$.zhh.parseOptions };
				}
				// 向服务器发送请求
				$.ajax(e);
			};
			var validateCallback = function() {
				if ($.isFunction(e.prehandle)) {
					e.prehandle.call(e, prehandleCallback);
				} else {
					prehandleCallback();
				}
			};

			if (e.form && $.isFunction(e.validate)) {
				// 表单校验
				e.validate.call(e, validateCallback);
			} else {
				validateCallback();
			}
		};
		if (e.form && $.isFunction(e.upload)) {
			// 文件上传
			e.upload.call(e, uploadCallback);
		} else {
			uploadCallback();
		}
	};

	o.fn.prepare = function() {
		if (this.data) {
			var contentType = this.contentType || $.ajaxSettings.contentType;
			if (contentType && /\bjson\b/.test(contentType)) {
				this.data = JSON.stringify(this.data);
			} else {
				if (o.fn.isDeepJson(this.data)) {
					this.data = $.zhh.jsonToParams(this.data);
				}
			}
		}
	};

	// 判断e.data是平面格式还是深度格式
	// 平面格式: { "address.city":"南京", "users[0].name":"张三" }
	// 深度格式: { "address":{"city":"南京"}, "users":[ { "name":"张三" } ] }
	o.fn.isDeepJson = function(data) {
		if (!$.isPlainObject(data) && !$.isArray(data)) { return false; }
		for (var key in data) {
			if ($.isPlainObject(data[key])) {
				return true;
			} else if ($.isArray(data[key])) {
				var array = data[key];
				for (var i = 0; i < array.length; i++) {
					if ($.isPlainObject(array[i])) {
						return true;
					} else if (o.fn.isDeepJson(array[i])) {
						return true;
					}
				}
			}
		}
		return false;
	};
	
	// 从form中读取请求数据, 如果有data则合并
	o.fn.readForm = function() {
		var e = this;

		if (!e.data) {
			e.data = e.form.serializeJson();
		} else {
			var deep = o.fn.isDeepJson(e.data);
			if (e.form) { $.extend(e.data, e.form.serializeJson(deep)); }
		}
	};

	var parseArgs = function() {
		var fields = "this, button, form, url, data, loading, succ, fail";
		var vars = $.zhh.parseArgs.call(this, arguments, fields, false, function(field, value) {
			if ($.dt.isFunction(value)) {
				return this.succ ? "fail" : "succ";
			} else if ($.dt.isBoolean(value)) {
				return "loading";
			} else if (isUrl(value)) {
				return "url";
			} else if ($.dt.isString(value)) {
				return "data"; // value = "key=value&x=xxx";
			} else if (isOptions(value)) {
				return { copy:value };
			} else if (isUrlObject(value, "GET")) {
				return { copy:{ type:"GET", url:value.GET } };
			} else if (isUrlObject(value, "POST")) {
				return { copy:{ type:"POST", url:value.POST } };
			} else if ($.dt.isPlainObject(value)) {
				if (!this.data) { this.data = $.extend(true, {}, value); }
				else { $.extend(true, this.data, value); }
			} else if ($.dt.isElement(value)) {
				var $elem = $(value);
				if ($elem.length) {
					return { field:$elem.is("form") ? "form" : "button", value:$(value) };
				}
			}
		});

		var e = $.extend(true, {}, o.options, vars);
		// 等于true的选项替换为默认的处理函数, { check:true, loading:{ show:true, stop:true }, ... }
		trueToDefault(e.loading, o.fn.loading);
		trueToDefault(e, o.fn);

		return e;
	};
	var isUrl = function(value) {
		// 字符串参数, 问号前面没有=就是url, 有=就是data
		return $.dt.isString(value) && /^[^\?\=]+(\?.*)?$/.test(value);
	};
	var isUrlObject = function(value, type) {
		return $.dt.isPlainObject(value) && (type in value);
	};
	var isOptions = function(value) {
		return $.dt.isPlainObject(value) && (value.O === true || value.OPTIONS === true || functionInJson(value));
	};
	var functionInJson = function(value) {
		if ($.dt.isPlainObject(value)) {
			for (var i in value) {
				if ($.dt.isFunction(value[i])) {
					return true;
				}
			}
		}
		return false;
	};
	var trueToDefault = function(e, def) {
		if (!$.dt.isPlainObject(e)) { return; }
		for (var key in def) {
			if (e[key] === true && !$.dt.isBoolean(def[key])) {
				e[key] = def[key];
			}
		}
	};
}(jQuery);


+function($) {
	/**
	 * -----------------------------------------------------------
	 * 动态加载JavaScript和CSS
	 * $.getScript()有个缺点, 动态加载的JS在DEBUG时找不到源码!
	 * -----------------------------------------------------------
	 * $.zhh.load("http://.../script/xxx.js");
	 * $.zhh.load("http://.../style/xxx.css");
	 * -----------------------------------------------------------
	 * author: 赵卉华 / 2015-12-19
	 * -----------------------------------------------------------
	 * 加载HTML
	 * $("xxx").zload("http://.../xxx.html");
	 * -----------------------------------------------------------
	 * author: 赵卉华 / 2016-09-27
	 * -----------------------------------------------------------
	 */

	var KEY = "__load_resource__";
	var QUERY = /[\?&#].*$/;
	var SCRIPTS = {};
	var STYLES = {};
	var OWNER = null;

	$(function() {
		OWNER = $(document.body);
		$("script[src]").each(function() {
			var url = $(this).attr("src");
			SCRIPTS[url.replace(QUERY, "")] = this;
		});
		$("link[href]").each(function() {
			var url = $(this).attr("href");
			STYLES[url.replace(QUERY, "")] = this;
		});
	});

	var loadCss = function(options, callback){
		var key = options.url.replace(QUERY, "");
		if (STYLES[key]) {
			if (callback) { callback.call(STYLES[key]); }
			return;
		}
		var link = document.createElement('link');
		link.href = options.url;
		link.rel = 'stylesheet';
		link.type = 'text/css';
		STYLES[key] = link;
		// 加载到appendTo或<head>中
		var parent = options.appendTo || document.getElementsByTagName("head");
		if (!parent.appendChild) { parent = parent[0]; }
		parent.appendChild(link);
		if (callback) { callback.call(link); }
	};
	
	var loadJs = function(options, callback) {
		var key = options.url.replace(QUERY, "");
		if (SCRIPTS[key]) {
			callback.call(SCRIPTS[key]);
			return;
		}
		var done = false;
		var script = document.createElement('script');
		script.src = options.url;
		script.type = 'text/javascript';
		script.onload = script.onreadystatechange = function(){
			if (!done && (!this.readyState || this.readyState == 'loaded' || this.readyState == 'complete')){
				done = true;
				this.onload = this.onreadystatechange = null;
				callback.call(this);
			}
		};
		SCRIPTS[key] = script;
		// 加载到appendTo或<head>中
		var parent = options.appendTo || document.getElementsByTagName("head");
		if (!parent.appendChild) { parent = parent[0]; }
		parent.appendChild(script);
	};

	var handleScriptAndStyle = function(text, rich) {
		// 解决动态加载的JS在DEBUG时找不到源码的问题!
		// 用setTimeout解决<link>加载完成之前,JS写入的<style>内容不生效导致宽高计算出错的问题!
		var ptn = /<(script|link)[^>]*(?:src|href)\s*=\s*['"]([^'">]+)['"][^>]*\/?>(?:\s*<\/(?:script|link)>)?/gim;
		var scripts = [];
		var links = [];
		var html = [];
		for (var i = 0, matcher = null; true;)  {
			matcher = ptn.exec(text);
			if (!matcher) {
				html.push(text.substring(i));
				break;
			}
			// 取index, 文档都讲有lastIndex, 但实际测试只有index(Chrome,FF)
			// IE有lastIndex(=matcher.index+matcher[0].length), (IE8,IE11)
			// http://www.w3schools.com/jsref/jsref_obj_regexp.asp
			// http://www.w3school.com.cn/jsref/jsref_obj_regexp.asp
			// http://www.w3school.com.cn/jsref/jsref_exec_regexp.asp
			// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp
			var index = matcher.index;
			if (!/data-zload-ignore=['"]true["']/ig.test(matcher[0])) {
				if (matcher[1].toLowerCase() == "script") {
					scripts.push(matcher[2]);
				} else {
					if (/rel=['"]stylesheet["']|type=['"]text\/css["']/ig.test(matcher[0])) {
						links.push(matcher[2]);
					}
				}
			}
			html.push(text.substring(i, index));
			html.push("<!-- " + matcher[0] + " -->");
			i = index + matcher[0].length;
		}
		if (rich) {
			var self = this;
			setTimeout(function() {
				$.zhh.load({ url:links, appendTo:self });
				$.zhh.load({ url:scripts, appendTo:document.body });
			}, 0);
		}
		return html.join("");
	};
	// 清除<!-- zload-ignore-start --><!-- zload-ignore-end -->之间的内容
	var htmlIgnoreFilter = function(text) {
		var ptn = /<!--\s*zload-ignore-start\s*-->((?:.|\r|\n)*?)<!--\s*zload-ignore-end\s*-->/gim;
		var scripts = [];
		var links = [];
		var html = [];
		for (var i = 0, matcher = null; true;)  {
			matcher = ptn.exec(text);
			if (!matcher) {
				html.push(text.substring(i));
				break;
			}
			var index = matcher.index;
			html.push(text.substring(i, index));
			// html.push("<!-- " + matcher[1] + " -->"); // matcher[1]之中有<!--注释-->时会有问题
			i = index + matcher[0].length;
		}
		return html.join("");
	};
	var loadHtml = function(options) {
		var self = options.appendTo;
		$(self).children("link[href]").each(function() {
			var url = $(this).attr("href");
			STYLES[url.replace(QUERY, "")] = undefined;
		});
		var originalDataFilter = options.dataFilter;
		var originalSuccess = options.success;
		var o = $.extend(true, {}, $.fn.zload.defaults.htmlOptions, options);
		o.dataFilter = function(data, type) {
			if (o.rich) {
				$(self).empty(); // 加载前清空内容
				// style可移除, 每次加载前重新查找已加载的link标签
				STYLES = {};
				$("link[href]").each(function() {
					var url = $(this).attr("href");
					STYLES[url.replace(QUERY, "")] = this;
				});
			}

			data = htmlIgnoreFilter.call(self, data, type);
			if (originalDataFilter) {
				data = originalDataFilter.call(self, data, type);
			}
			if (data.length == 0) { return data; }
			var bodyRule = $.isArray(o.bodyRule) ? o.bodyRule : [o.bodyRule];
			var html = handleScriptAndStyle.call(self, data, o.rich);
			// 取<body>的内容
			for (var i = 0; i < bodyRule.length; i++) {
				var matcher = bodyRule[i].exec(html);
				if (matcher) {
					return matcher[1];
				}
			}
			return html;
		};
		o.success = function(data) {
			$(self).html(data);
			// 队列, 以保证加载完JS再执行初始化
			setTimeout(function() {
				OWNER.promise(KEY).done(function() {
					$(self).zinit();
					if (originalSuccess) {
						originalSuccess.call(self);
					}
				});
			}, 0);
		};
		$.ajax(o);
	};

	var loadRes = function(options, callback) {
		if (/\.css(\?.*)?$/i.test(options.url)) {
			loadCss(options, callback);
		} else if (/\.js(\?.*)?$/i.test(options.url)) {
			// 队列, 以保证js的加载顺序
			OWNER.queue(KEY, function() {
				loadJs(options, function() {
					if (callback) { callback.call(this); }
					OWNER.dequeue(KEY);
				});
			});
		} else {
			loadHtml(options, callback);
		}
	};
	
	if (!$.zhh) { $.zhh = {}; }
	$.zhh.load = function(options, callback) {
		if (typeof(options) == "string" || $.isArray(options)) {
			options = { url:options };
		}
		if ($.isArray(options.url)) {
			$.each(options.url, function(i, s) {
				loadRes($.extend({}, options, { url:s }), callback);
			});
		} else {
			loadRes(options, callback);
		}
		OWNER.dequeue(KEY);
	};

	$.fn.zload = function(options, callback) {
		if (typeof(options) == "string" || $.isArray(options)) {
			options = { url:options };
		}
		options.appendTo = this[0];
		$.zhh.load(options, callback);
		return this;
	};
	$.fn.zload.defaults = {
		htmlOptions: {
			type:"GET",
	        rich: true, // 是否加载样式和脚本
	        dataType: "html",
	        bodyRule: /<body[^>]*>((?:.|\r|\n)*?)<\/body>/im
		}
	};
}(jQuery);


/**
 * -----------------------------------------------------------
 * 插件初始化, 让所有的初始化处理都可以多次调用
 * -----------------------------------------------------------
	// 初始化非jQuery插件, 以前这么写
	if (window.baidu && baidu.template) {
		// 设置左右分隔符为 <# #>
		baidu.template.LEFT_DELIMITER='<#';
		baidu.template.RIGHT_DELIMITER='#>';
	}
	// 现在要改成这样, 成功之后要返回true
	$.fn.zinit.plugins.add(function() {
		if (window.baidu && baidu.template) {
			// 设置左右分隔符为 <# #>
			baidu.template.LEFT_DELIMITER='<#';
			baidu.template.RIGHT_DELIMITER='#>';
			return true;
		}
	});
 * -----------------------------------------------------------
	// 初始化jQuery插件, 以前这么写
	$.fn.datepicker.defaults.format = "yyyy-mm-dd";
	$.fn.datepicker.defaults.language = "zh-CN";
	// 现在要改成这样
	$.fn.zinit.plugins.add("datepicker", function() {
		this.defaults.format = "yyyy-mm-dd";
		this.defaults.language = "zh-CN";
	});
 * -----------------------------------------------------------
	// 初始化jQuery节点, 以前这么写
	$(function() {
	    $(".datepicker").each(function() {
	    	var me = $(this);
	    	me.datepicker(me.zoptions());
	    });
	});
	// 现在要改成这样
	$.fn.zinit.nodes.add(".datepicker", "datepicker");
	// 或者
	$.fn.zinit.nodes.add(".datepicker", function() {
		var me = $(this);
		me.datepicker(me.zoptions());
	});
 * -----------------------------------------------------------
 * author: 赵卉华 / 2016-09-27
 * -----------------------------------------------------------
 */
+function($) {
	var KEY = "__z_init__"; // 防重复初始化
	var panels = []; // { type:string } or { callback:function }
	var nodes = []; // { selector:string, type:string } or { selector:string, callback:function }
	var plugins = []; // { type:string } or { callback:function }
	$.fn.zinit = function(callback) {
		$.each(plugins, function() {
			execPluginInit(this);
		});

		var box = this;
		$.each(nodes, function() {
			var selector = this.selector;
			var type = this.type;
			var callback = this.callback;
			if (type) {
				// $.fn.zinit.nodes.add(".datepicker", "datepicker");
				if ($.fn[type]) {
					box.find(selector).each(function() {
						var me = $(this);
						if (me.data(KEY + type)) { return true; }
						me.data(KEY + type, true);
						me[type](me.zoptions());
					});
				}
			} else {
				// $.fn.zinit.nodes.add(".datepicker", function() {});
				box.find(selector).each(function() {
					var me = $(this);
					if (type) {
						if (me.data(KEY + type)) { return true; }
						me.data(KEY + type, true);
					}
					callback.call(this);
				});
			}
		});
		// 直接用box调用callback, 不控制重复加载
		// 适用于收集多个控件的参数, 一次性向服务器请求数据, 然后批量初始化控件的情况
		$.each(panels, function() {
			var callback = this.callback;
			if (callback) {
				// $.fn.zinit.panels.add(function() {});
				box.each(function() {
					callback.apply(this);
				});
			} else {
				// $.fn.zinit.panels.add("webinput");
				var type = this.type;
				if ($.fn[type]) {
					box.each(function() {
						var me = $(this);
						me[type](me.zoptions());
					});
				}
			}
		});
		if (callback) { callback.call(this); };
	};
	var execPluginInit = function(plugin) {
		var type = plugin.type;
		var callback = plugin.callback;
		if (plugin.disabled != true) {
			if (type) {
				// $.fn.zinit.plugins.add("datepicker", function() {});
				if ($.fn[type]) {
					plugin.disabled = true;
					callback.call($.fn[type]);
				}
			} else {
				// $.fn.zinit.plugins.add(function() {});
				plugin.disabled = callback();
			}
		}
	};
	$.fn.zinit.nodes = {
		add: function(selector, type) {
			if ($.isFunction(type)) {
				nodes.push({ selector:selector, callback:type });
			} else {
				nodes.push({ selector:selector, type:type });
			}
		}
	};
	$.fn.zinit.panels = {
		add: function(type) {
			if ($.isFunction(type)) {
				panels.push({ callback:type });
			} else if ($.dt.isString(type)) {
				panels.push({ type:type });
			}
		}
	};
	$.fn.zinit.plugins = {
		add: function(type, callback) {
			if ($.isFunction(type)) {
				var plugin = { callback:type };
				execPluginInit(plugin);
				plugins.push(plugin);
			} else if ($.dt.isString(type) && $.isFunction(callback)) {
				var plugin = { type:type, callback:callback };
				execPluginInit(plugin);
				plugins.push(plugin);
			}
		}
	};
}(jQuery);



/**
 * jQuery插件初始化函数
 * ------------------------------
	$.fn.horizTabs = $.zhh.jqplugin("HorizTabs", function(element, options) {
		return new HorizTabs(element, options);
	});
	$.fn.horizTabs = $.zhh.jqplugin("HorizTabs", "HORIZ_TABS", function(element, options) {
		return new HorizTabs(element, options);
	});
	$.fn.horizTabs = $.zhh.jqplugin({
		name: "HorizTabs",
		dataKey: "HORIZ_TABS", 
		defaultCall: "method", // 参数为undefined或{}时调用什么方法
		construct: function(element, options) {
			return new HorizTabs(element, options);
		}
	});
 * ------------------------------
	options = {
		name: string, // 插件名称, required
		dataKey: string, // 保存插件实例的KEY, optional, 默认为name
		construct: function(element, options) // 插件构造函数, required
		getter: function(method) // 判断是否为Getter的函数
		intercept: function(options) {} // 拦截处理, return { skip:boolean, result:anything }
	}
 * ------------------------------
 * @author zhaohuihua 2019-04-09
 * ------------------------------
 */
+function($) {
	$.zhh.jqplugin = function(name, dataKey, construct) {
		var inParams;
		if ($.isPlainObject(name)) {
			inParams = name;
		} else {
			if ($.isFunction(dataKey)) {
				inParams = { name:name, construct:dataKey };
			} else {
				inParams = { name:name, dataKey:dataKey, construct:construct };
			}
		}
		var o = $.extend(true, {}, $.zhh.jqplugin.defaults, inParams);
		var dataKey = o.dataKey || o.name;
		return function(options) {
			if ($.isFunction(inParams.intercept)) {
				var resp = inParams.intercept.apply(this, arguments);
				if (resp && resp.skip === true) { return resp.result; }
			}
			var args = [];
			if ($.dt.isString(options)) {
				for (var i = 1; i < arguments.length; i ++) {
					args.push(arguments[i]);
				}
			}
			var exec = function() {
				var me = $(this);
				var api = me.data(dataKey);
				if (!api) {
					var temp = $.extend(true, {}, me.zoptions(), $.isPlainObject(options) && options);
					me.data(dataKey, (api = o.construct(this, temp)));
				}
				if ($.dt.isString(options)) {
					if (options == "api") {
						return api;
					} else if (options == "options") {
						return api.options;
					} else {
						var fn = api[options];
						if ($.isFunction(fn)) {
							return fn.apply(api, args);
						} else {
							console.warn("Plugin " + name + "." + options + " not found. ");
						}
					}
				} else if (o.defaultCall) {
					var fn = api[o.defaultCall];
					if ($.isFunction(fn)) {
						return fn.apply(api);
					} else {
						console.warn("Plugin " + name + "." + o.defaultCall + " not found. ");
					}
				}
			};
			if (o.getter(options)) {
				return exec.call(this.get(0));
			} else {
				return this.each(function () {
					exec.call(this);
				});
			}
		};
	};
	$.zhh.jqplugin.defaults = {
		getter: function(method) {
			return method && (method == "api" || method == "options" || /^(get|is|find)[A-Z]/.test(method));
		}
	};
}(jQuery);


/**
 * -----------------------------------------------------------
 * 定时检测文本框内容实现zchanged事件
 * -----------------------------------------------------------
 * // 初始化事件绑定
 * $("input[name=account]").zchanged(function(newValue, oldValue) {
 *     // do something
 * });
 * // 手工启动定时检测
 * $("input[name=account]").zchanged("start");
 * // 手工停止定时检测
 * $("input[name=account]").zchanged("stop");
 * // 强制触发
 * $("input[name=account]").zchanged("trigger");
 * -----------------------------------------------------------
 * // 正常情况下, 定时检测只在文本框获取焦点时启动
 * // 但如果存在密码输入框, 由于浏览器有记住密码的功能, 只好在初始化后200毫秒时自动启动一次
 * form.find("input[type=text],input[type=password]").zchanged(function(newValue, oldValue) {
 *     // do something
 * });
 * -----------------------------------------------------------
 * author: 赵卉华 / 2017-09-01
 * -----------------------------------------------------------
 */
+function($) {
	$.fn.zchanged = function(type, fn) {
		var args = arguments;
		if ($.isFunction(type)) {
			fn = type; type = "init"; args = [type, fn];
		}
		var hasPwdInput = this.filter("[type=password]");
		return this.each(function() {
			if (type == "init") {
				zv.methods.init.call(this, fn);
				if (hasPwdInput) { // 如果存在密码输入框, 由于浏览器有记住密码的功能, 只好在初始化后200毫秒时自动启动一次
					var self = this;
					setTimeout(function() {
						zv.methods.check.call(self);
					}, 200);
				}
			} else {
				var method = zv.methods[type];
				if (!method) {
					$.log.error("$.fn.zchanged.defaults.methods." + type + " not found!");
					return this;
				}
				return method.apply(this, Array.prototype.splice.call(args, 1));
			}
		});
	};
	var zv = $.fn.zchanged.defaults = {
		keys: { timer: "__z_changed_timer__", value:"__z_changed_last_value__", callabck:"__z_changed_callabck__" },
		methods: {
			init: function(fn) { // 初始化事件绑定: 获得焦点时开始定时检测, 失败焦点时停止检测
				zv.methods.stop.call(this);
				var me = $(this);
				fn && me.data(zv.keys.callabck, fn);
				me.off(".zchanged")
				.on("focus.zchanged", function() {
					zv.methods.start.call(this);
				})
				.on("blur.zchanged", function() {
					zv.methods.stop.call(this);
				});
				if (me.attr("autofocus")) {
					zv.methods.start.call(this);
				}
				// 初始化时检查文本框的值是否有变化
				var defvalue = me.prop("defaultValue");
				var currvalue = me.val();
				me.data(zv.keys.value, defvalue);
				if (defvalue != currvalue) {
					zv.methods.check.call(this);
				}
			},
			start: function() { // 开始检测
				var self = this;
				var me = $(self);
				var fn = me.data(zv.keys.callabck);
				var timer = me.data(zv.keys.timer);
				if (timer) { window.clearInterval(timer); }
				timer = window.setInterval(function() {
					zv.methods.check.call(self, fn);
				}, 100);
				me.data(zv.keys.timer, timer);
			},
			stop: function() { // 停止检测
				var me = $(this);
				var fn = me.data(zv.keys.callabck);
				fn && zv.methods.check.call(this, fn);
				me.data(zv.keys.value, me.val());
				var timer = me.data(zv.keys.timer);
				if (timer) { window.clearInterval(timer); me.removeData(zv.keys.timer); }
			},
			check: function() { // 执行检测
				var me = $(this);
				var fn = me.data(zv.keys.callabck);
				var o = me.data(zv.keys.value) || "";
				var n = me.val();
				if (o != n) {
					me.data(zv.keys.value, n);
					fn && fn.call(this, n, o);
				}
			},
			trigger: function() { // 强制触发
				var me = $(this);
				var fn = me.data(zv.keys.callabck);
				var o = me.data(zv.keys.value) || "";
				var n = me.val();
				fn && fn.call(this, n, o);
			},
			resetValue: function(value) {
				var me = $(this);
				if (value == undefined) {
					value = me.val();
				} else {
					me.val(value);
				}
				me.data(zv.keys.value, value);
			}
		}
	};
}(jQuery);


+function($) {
	// 开始倒计时1:
	// $(xxx).countdown("{second}后重试", 10, function() {  });
	// 开始倒计时2:
	// <a class="send" data-countdown="发送({second})">发送</a>
	// $("a.send").countdown(10, function() {  });
	// 结束倒计时
	// $(xxx).countdown("clear");
	$.fn.countdown = function(hint, times, fn) {
		return this.each(function() {
			var dom = $(this);
			if (typeof(hint) == "number") {
				fn = times; times = hint;
				hint = dom.data("countdown") || "{second}";
			}
			var original = dom.data("original-html");
			if (!original) {
				original = dom.html();
				dom.data("original-html", original);
			}
			var timer = dom.data("countdown-timer");
			if (timer) { window.clearInterval(timer); }
			if (hint == "clear") {
				dom.html(original);
			} else {
				var exec = function() {
					var text = hint.replace(/\{second\}/g, times--);
					dom.html(text);
					if (times == 0) {
						clearInterval(timer);
						dom.html(original);
						fn && fn.call(dom);
					}
				};
				exec();
				timer = setInterval(exec, 1000);
				dom.data("countdown-timer", timer);
			}
		});
	};
}(jQuery);



/**
 * -----------------------------------------------------------
 * 国际化多语言选择
 * https://www.w3.org/International/articles/bcp47/
 * 由于语言代码的复杂性, 我们不可能为每个语言静态化一个版本, 所以必需进行转换
 * 如汉语, 可分为简体版zh-CN和繁体版zh-CHT, 那么如下的语言+地区列表就需要转换到这两个版本
 * zh-HK 汉语(香港)
 * zh-MO 汉语(澳门)
 * zh-TW 汉语(台湾)
 * zh-SG 汉语(新加坡)
 * zh-CHS 汉语(简体)
 * zh-CHT 汉语(繁体)
 * zh-Hans 汉语(简体)
 * zh-Hant 汉语(繁体)
 * zh-cmn-Hans-CN cmn:Mandarin Chinese(普通话), Hans:Simplified script(简体), CN:used in China
 * IETF新标准的zh-Hans(汉语简体)/zh-Hant(汉语繁体), 后面会带国家或地区如zh-Hans-CN
 * -----------------------------------------------------------
 * // 只支持中英文两个版本
 * $.zhh.resolveLocale([ "zh-CN", "en" ]); // 所有zh开头的language都会自动查找第一个以zh开头的支持版本,从而zh-CHS/zh-CHT/...都能找到zh-CN
 * // 英文优先
 * $.zhh.resolveLocale("en", [ "zh-CN", "en" ]);
 * // def必须存在于locales中, 否则无效, 如下代码jp无效
 * $.zhh.resolveLocale("jp", [ "en", "zh-CN", "de" ]);
 * // 支持英文/中文简体/中文繁体, JSON形式的KEY是实际支持的Locale, value是该Locale兼容的Locale(同义词)
 * $.zhh.resolveLocale([ "en", {"zh-CN":"zh-Hans,zh-CHS,zh-SG"}, {"zh-TW":"zh-Hant,zh-HK,zh-MO,zh-TW"} ]);
 * // 中文最合理的方式应该是分为zh-CHS(简体)和zh-CHT(繁体), 而不是zh-CN和zh-TW
 * // 中国大陆适用简体, 新加坡/马来西亚等也适用简体(来自百度), 中国台湾/中国香港/中国澳门适用繁体
 * $.zhh.resolveLocale([ "en", {"zh-CHS":"zh-Hans,zh-CN,zh-SG"}, {"zh-CHT":"zh-Hant,zh-HK,zh-MO,zh-TW"} ]);
 * -----------------------------------------------------------
 * author: 赵卉华 / 2017-09-01
 * -----------------------------------------------------------
 */
+function($) {
	if (!$.zhh) { $.zhh = {}; }
	$.zhh.resolveLocale = function(def, locales) { // def必须存在于locales中, 否则无效
		if ($.isArray(def)) { localeGetter = locales; locales = def; def = undefined; }
		var realLocale = zv.fn.getCookieLocale() || zv.fn.getBrowserLocale();
		// [ "en", {"zh-CHS":"zh-Hans,zh-CN,zh-SG"}, {"zh-CHT":"zh-Hant,zh-HK,zh-MO,zh-TW"} ]
		// first: string, 列表中的第一项的Locale
		// languages: { "en":"en", "zh":"zh-CHS" } // 列表每一项中最先出现的Language对应的Locale
		// locales: { "en":"en", "zh-chs":"zh-CHS", "zh-hans":"zh-CHS", "zh-cn":"zh-CHS",  ... }
		var cache = { first:null, languages:{}, locales:{} };
		for (var i = 0; i < locales.length; i++) {
			var locale = locales[i];
			if (!$.isPlainObject(locale)) {
				addToCache(cache, locale, locale);
			} else {
				for (var key in locale) {
					addToCache(cache, key, key);
					var values = $.isArray(locale[key]) ? locale[key] : locale[key].split(/,/);
					for (var v = 0; v < values.length; v++) {
						addToCache(cache, key, values[v]);
					}
				}
			}
		}
		// zh-Hans-CN,zh-Hans;q=0.8
		// 替换掉分号后面的内容, 再以逗号拆分
		var expectedLocales = zv.fn.formatLocale(realLocale.replace(/;.+/,"")).split(/,/);
		if (def && $.inArray(def, expectedLocales) < 0) { expectedLocales.push(def); }
		var expectedLanguages = { map:{}, list:[] };
		for (var i = 0; i < expectedLocales.length; i ++) {
			var expectedLocale = expectedLocales[i];
			var formatted = zv.fn.formatLocale(expectedLocale);
			if (cache.locales[formatted]) { // 完全匹配
				return cache.locales[formatted];
			}
			// zh-Hans-CN
			var temp = formatted.split(/\-/);
			var language = temp[0]; // 第一段是语言: zh
			var subtags = temp.slice(1); // 其他标签: Hans, CN
			if (cache.locales[language]) { // language直接匹配, 如en
				return cache.locales[language];
			}
			// 语言与其他标签一一组合尝试: zh-Hans, zh-CN
			for (var s = 0; s < subtags.length; s++) {
				var key = language + "-" + subtags[s];
				if (cache.locales[key]) { return cache.locales[key]; }
			}
			// 没有匹配的, 根据Language返回该Language最先出现的Locale
			// Locale的第一段是Language, 如zh-CN的Language=zh
			// 如: [ "en", {"zh-CHS":"zh-Hans,zh-CN,zh-SG"}, {"zh-CHT":"zh-Hant,zh-HK,zh-MO,zh-TW"} ]
			// expectedLocale = zh-NEW, 此时根据zh推断返回最先出现的zh-CHS
			if (cache.languages[language]) {
				return cache.languages[language];
			}
		}
		// def传入null可以在未匹配时返回null, 否则返回列表中的第一项
		return def === null ? null : cache.first;
	};
	var addToCache = function(cache, original, synonym) {
		if (original == null || synonym == null) { return; }
		var formatted = zv.fn.formatLocale(synonym);
		if (!cache.first) { cache.first = original; }
		if (!cache.locales[formatted]) { cache.locales[formatted] = original; }
		var language = formatted.split(/\-/)[0];
		if (!cache.languages[language]) { cache.languages[language] = original; }
	};
	var zv = $.zhh.resolveLocale.defaults = {
		i18nCookieKey: "I18N",
		fn : {
			formatLocale: function(locale) {
				return locale.toLowerCase().replace(/[^0-9a-zA-Z]/g, "-"); // 非数字字母的字符都替换为横杠
			},
			getBrowserLocale: function() { // 从浏览器设置中获取Locale
				return window.navigator.language || window.navigator.browserLanguage;
			},
			getCookieLocale: function() { // 从Cookie中读取Locale
				var cookies = document.cookie ? document.cookie.split('; ') : [];
				for (var i = 0; i < cookies.length; i++) {
					var parts = cookies[i].split('=');
					if (parts[0] == zv.i18nCookieKey) {
						return parts[1];
					}
				}
			}
		}
	};
}(jQuery);


/*
// https://www.cnblogs.com/diligenceday/p/4246515.html
// JS.Class 是一个mootools式的类工厂 基于 lunereaper dawid.kraczkowski@gmail.com 的项目进行修改
	var Animal = JS.Class({
		construct: function(name) {
			this.name = name;
		},
		shout: function(s) {
			console.log(s);
		}
	});
	var animal = new Animal();
	animal.shout('hello'); // hello
	
	var Dog = Animal.extend({
		construct: function(name, color) {
			this.parent.construct.apply(this, arguments); // 调用父类构造器
			this.color = color;
		},
		run: function() {
			console.log(this.name + " running");
		}
	});
	var dog = new Dog("jack", "black");
	dog.shout('hello'); // hello
	dog.run(); // jack running
	
	var Shepherd = Dog.extend({
		statics: { // 静态成员
			TYPE: "Shepherd"
		},
		run: function() {
			// this.parent.run.call(this); // 方法链,调用超类同名方法
			console.log("[Shepherd] " + this.name + " running");
		}
	});
	console.log(Shepherd.TYPE); // Shepherd
	var shepherd = new Shepherd("john", "white");
	shepherd.run(); // [Shepherd] john running
*/
(function (global) {
	"use strict";

	var JS = global.JS = {};
	JS.Class = function(classDefinition) {

		// 返回目标类的真正构造器
		function getClassBase() {
			return function() {
				// 它在里面执行用户传入的构造器construct
				// preventJSBaseConstructorCall是为了防止在createClassDefinition辅助方法中执行父类的construct
				if (typeof this['construct'] === 'function' && preventJSBaseConstructorCall === false) {
					this.construct.apply(this, arguments);
				}
			};
		}
		// 为目标类添加类成员与原型成员
		function createClassDefinition(classDefinition) {
			// 此对象用于保存父类的同名方法
			var parent = this.prototype["parent"] || (this.prototype["parent"] = {});
			for (var prop in classDefinition) {
				if (prop === 'statics') {
					for (var sprop in classDefinition.statics) {
						this[sprop] = classDefinition.statics[sprop];
					}
				} else {
					// 为目标类添加原型成员, 如果是函数, 那么检测它还没有同名的超类方法
					if (typeof this.prototype[prop] === 'function') {
						var parentMethod = this.prototype[prop];
						parent[prop] = parentMethod;
					}
					this.prototype[prop] = classDefinition[prop];
				}
			}
		}

		// 其实就是这样的Base = function() {};别想太多了
		var preventJSBaseConstructorCall = true;
		var Base = getClassBase();
		preventJSBaseConstructorCall = false;

		createClassDefinition.call(Base, classDefinition);

		// 用于创建当前类的子类
		Base.extend = function(classDefinition) {

			// 其实就是这样的SonClass = function() {};别想太多了
			preventJSBaseConstructorCall = true;
			var SonClass = getClassBase();
			SonClass.prototype = new this(); // 将一个父类的实例当作子类的原型
			preventJSBaseConstructorCall = false;

			createClassDefinition.call(SonClass, classDefinition);
			SonClass.extend = this.extend;

			return SonClass;
		};
		return Base;
	};
})(this);

